How to Integrate Astro With ApostropheCMS pt. 1
Tags: javascript, tutorial, devops, opensource
Published: 2024-03-21 16:12:02

Apostrophe recently announced the [integration for the Astro web framework](https://apostrophecms.com/blog/introducing-the-apostrophe-astro-integration), one of the hottest technologies in the IT community. Astro has become so popular because it offers developers the ability to create efficient and SEO-oriented web applications using multiple UI frameworks like React and Vue.js, even on the same project.

In the first part of this two-article series, you will learn what Astro is, what the ApostropheCMS Astro Integration Starter Kit is, and how to use it to manage the pages and content of an Astro frontend via ApostropheCMS.

## What Is Astro?

[Astro](https://astro.build/) is an open-source JavaScript framework known for its versatility, performance, and new approach to web development. It enables developers to create fast, modern, content-rich web applications and sites using the "Bring Your Own Framework" (BYOF) model. 

What makes Astro special is its ability to work with multiple JavaScript frameworks at the same time, including React, Preact, Svelte, Vue.js, and others. This gives developers the flexibility to choose the tools they prefer for any specific goal. For example, one UI component might be in Vue and another in React. In Astro, that is entirely possible.

Astro relies on an innovative "[Island Architecture](https://docs.astro.build/en/concepts/islands/)" that separates static content from interactive parts of a web page. This architecture allows you to mix static content, server-rendered components, and client-rendered components on the same page without conflict. Astro will take care of sending the minimum amount of JavaScript required for interactivity on the page.

The framework also offers several other features, such as top-level waiting, support for Markdown and MDX, and themes, making it a complete solution for modern web development needs.

## Integrate ApostropheCMS With Astro With the Starter Kit

Integrating ApostropheCMS into an Astro application means:

- Let ApostropheCMS manage the content, handle URL routing, and content retrieval.
- Let Astro take responsibility for page rendering and implementing the UI logic using a frontend framework of your choice, such as [React, Vue.js, or Svelte](https://docs.astro.build/en/guides/integrations-guide/).

Note that this integration is possible because ApostropheCMS is unopinionated on the frontend. For example, you can also integrate it with new technologies such as HTMX. Learn more in our [HTMX integration guide](https://apostrophecms.com/blog/htmx-examples-and-how-to-use-it).

The easiest way to achieve the Astro integration is through the [ApostropheCMS Astro Integration Starter Kit](https://apostrophecms.com/starter-kits/astro-integration-starter-kit). This module uses the `@apostrophecms/apostrophe-astro` npm library to bring the ApostropheCMS Admin UI to your Astro application. That way, you can manage your Astro site exactly as if you were in a regular ApostropheCMS application.

Given the dual nature of this setup, you need two projects to get the most out of the starter kit module. These are:

1. **An ApostropheCMS project**: This is where you define your page types, widget types, and other content types with their schemas and other customizations. 
2. **An Astro project**: This is where you write your templates and frontend code.

Let’s now see how to set up these two projects and integrate ApostropheCMS into Astro.

## Set Up an ApostropheCMS Project for Astro

The recommended way to set up an ApostropheCMS project for Astro integration is to clone the `apostrophecms/starter-kit-astro` project:

```
git clone apostrophecms/starter-kit-astro.git
```

Or, equivalently, create a new Apostrophe project using the Astro starter kit:

```
apos create my-apos-project-name --starter=astro
```

The main benefit of starting with the provided project is that it contains an ApostropheCMS blog application that has already been configured for integration with an Astro frontend. This will save you a lot of time.
If you want to use your ApostropheCMS project, make sure to:

- Be using Apostrophe >= 3.x.
- Run `npm update` to bring your project to the latest version of `apostrophe`.
- Update any page templates in your Apostrophe project to provide a link to your Astro frontend site and remove all other output. Everyone who accesses the backend, editors included, should go straight to the Astro frontend.

Well done! You now have an ApostropheCMS project for Astro. From now on, we will assume that your ApostropheCMS project is in the `starter-kit-astro` folder.

## Set Up an Astro Project for ApostropheCMS

Follow the steps below and learn how to set up an Astro frontend project that integrates with an ApostropheCMS backend.
 
**Initialize an Astro Project**

The recommended way to create an Astro-based site that relies on ApostropheCMS for content editing is to clone the `apostrophecms/astro-frontend` repository:

```
git clone https://github.com/apostrophecms/astro-frontend.git
```

This project will already contain all the configurations, components, and templates to connect Astro to the ApostropheCMS Astro starter kit. Again, the main advantage of using the provided project is that almost everything needed to run Astro with AspotropheCMS is done there. So, you can get a huge head start.

If you instead prefer to start from scratch, run the command below and follow the wizard to [initialize a new Astro project](https://docs.astro.build/en/tutorial/1-setup/2/):

```
npm create astro@latest
```

Then, add `@apostrophecms/apostrophe-astro` to your project dependencies with:

```
npm install @apostrophecms/apostrophe-astro
```

As mentioned before, this module allows you to integrate ApostropheCMS into your Astro application.

Great! You now have an Astro project for ApostropheCMS. From now on, we will assume that your Astro project is in the `astro-frontend` folder.

**Add an Astro Configuration File**

To make Astro communicate with the Apostrophe backend, you need to customize how Astro works. To do so, add an `astro.config.mjs` file to the project's root folder. Note that most Astro template projects—including `apostrophecms/astro-frontend`—come with a working configuration file. But if you are not starting from our `astro-frontend` project, then you will need to add Apostrophe-related configuration yourself.

This is what your `astro.config.mjs` configuration file should look like:

```
// ./astro.config.mjs

import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import apostrophe from '@apostrophecms/apostrophe-astro';

export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone'
  }),
  integrations: [
    apostrophe({
      aposHost: 'http://localhost:3000',
      widgetsMapping: './src/widgets',
      templatesMapping: './src/templates',
      forwardHeaders: [
        'content-security-policy', 
        'strict-transport-security', 
        'x-frame-options',
        'referrer-policy',
        'cache-control',
        'host'
      ]
    })
  ],
  vite: {
    ssr: {
      // Do not externalize the @apostrophecms/apostrophe-astro plugin, we need
      // to be able to use virtual: URLs there
      noExternal: [ '@apostrophecms/apostrophe-astro' ],
    }
  }
});
```
To give editors the ability to edit the content directly on the page, the `@apostrophecms/apostrophe-astro` module requires Astro to be configured in SSR (Server-Side Rendering) mode. In detail, the `output:` `'``server``'` setting enables on-demand rendering on the ApostropheCMS server at request time. As of this writing, support for SSG (Static Site Generation) is under consideration for the future.

What you should focus your attention on is the object passed to the `apostrophe()` integration function. That specifies the following options:

- `aposHost`: The base URL of your ApostropheCMS instance. When developing locally or contacting an ApostropheCMS instance on the same server, the URL must contain the port number. During development, its value should be `http://localhost:3000`. You can override this option through the `APOS_HOST` environment variable.
- `widgetsMapping`: The optional path to the JavaScript file in your Astro project that contains the mapping between Apostrophe widget types and your Astro components.
- `templatesMapping`: The optional path to the JavaScript file in your Astro project that contains the mapping between Apostrophe templates and your Astro templates.
- `forwardHeaders`: An optional array of HTTP headers that you want to forward from the ApostropheCMS backend to the final response sent to the browser. This is useful if your backend is using a module like `@apostrophecms/security-headers` and wants to keep the headers you configured in ApostropheCMS.

For more information on all supported options, [see the documentation](https://github.com/apostrophecms/apostrophe-astro?tab=readme-ov-file#options).

Now, you may be wondering what the widget mapping file and the template mapping file are. Find that out in the next sections.

**Mapping Apostrophe Widgets to Astro Components**

The core element of the in-context editing experience offered by ApostropheCMS revolves around areas. These are special sections of the page where editors can add one or more content widgets. A [widget](https://v3.docs.apostrophecms.org/guide/areas-and-widgets.html) is a section of structured content, such as a block of rich text, an image, or a video. You can configure which widgets are allowed in a specific area in the ApostropheCMS [field schema](https://v3.docs.apostrophecms.org/guide/content-schema.html) of a page or piece type. 

For example, these are the widgets supported by the `area` field of the `blog` page type in the `apostrophecms/starter-kit-astro` project:


```
widgets: {
  '@apostrophecms/rich-text': {
   // extra configurations...
  },
  '@apostrophecms/image': {},
  '@apostrophecms/video': {},
  'two-column': {}
}
```

When an editor clicks on the “+ Add Content” button of that area, these will be the corresponding options that they will see:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59dor9ph3lncur3s6cuw.png)

To enable Astro to render the areas received from ApostropheCMS, you must define an Astro component for each ApostropheCMS widget in use. This is true even for basic widget types like `@apostrophecms/image`.

As a best practice, you should place all Astro widget components in the `./src/widgets` folder of your project.

An [Astro component](https://docs.astro.build/en/basics/astro-components/) is nothing more than a `.astro` file that contains some reusable UI elements. For example, this is how you could define the Astro component for the `@apostrophecms/image` widget:

```
---
// ./src/widgets/ImageWidget.astro

const { widget } = Astro.props;
const placeholder = widget?.aposPlaceholder;
const src = placeholder ?
  '/images/image-widget-placeholder.jpg' :
  widget?._image[0]?.attachment?._urls['full'];
---
<style>
  .img-widget {
    width: 100%;
  }
</style>
<img class="img-widget" {src} />
```

This will take care of rendering in an HTML `<img>` tag the image contained in the widget or a placeholder. For more information on how this component works, [check out the documentation](https://github.com/apostrophecms/apostrophe-astro?tab=readme-ov-file#creating-astro-widget-components).

The `apostrophecms/astro-frontend` project currently ships only with the following Astro widget components:

- `RichTextWidget`
- `ImageWidget`
- `VideoWidget`
- `TwoColumnWidget`

If you use other widgets, you will have to manually add a component.

Once you have defined all the required Astro widget components, you need to list them in the mapping file specified in the aforementioned `widgetsMapping` option. In this case, this is what the `./src/widgets/index.js` mapping file should contain:

```
// ./src/widgets/index.js

import RichTextWidget from './RichTextWidget.astro';
import ImageWidget from './ImageWidget.astro';
import VideoWidget from './VideoWidget.astro';
import TwoColumnWidget from './TwoColumnWidget.astro';

const widgetComponents = {
  '@apostrophecms/rich-text': RichTextWidget,
  '@apostrophecms/image': ImageWidget,
  '@apostrophecms/video': VideoWidget,
  'two-column': TwoColumnWidget
};

export default widgetComponents;

```

As you can see, this file imports all the Astro widget component files, associates them with the equivalent ApostropheCMS widget types in a mapping object, and exports it. 

**Mapping Apostrophe Templates to Astro Components**

In ApostropheCMS, templates are where code and content become web pages. Specifically, templates are written in normal HTML markup with special tags and are based on the [Nunjucks template language](https://mozilla.github.io/nunjucks/). Thus, they are `.html` files placed in the `/views` subfolder of an ApostropheCMS module.

Do not forget that the frontend of this application is handled entirely by Astro. So, just like what happened with widgets, you need to create an Astro component corresponding to each template that ApostropheCMS would normally render with Nunjucks.

Keep in mind that a template represents only a part of an entire web page. In other words, you do not want Astro to treat those components as routes. To prevent them from becoming [Astro pages](https://docs.astro.build/en/basics/astro-pages/), avoid placing them in the `./src/pages` folder. Instead, you should place your Astro template components in the `./src/templates` directory.

Now, let’s look at how to write Astro template components. This is what a simple component for the template of the “default” page might look like:

```
---
// ./src/templates/DefaultPage.astro

import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro';
const { page, user, query } = Astro.props.aposData;
const { main } = page;
---

<section class="bp-content">
  <h1>{ page.title }</h1>
  <AposArea area={main} />
</section>
```

Notice that the `Astro.props.aposData` object received from ApostropheCMS gives you access to `page`, `user`, `query`, and other useful data. In particular, `page` represents the Nunjucks template and contains the `area` field of the page called `main`.

`AposArea` is a special component from the `@apostrophecms/apostrophe-astro` package that gives Astro the ability to render ApostropheCMS areas. That means it provides the standard ApostropheCMS in-context editing experience to the Astro frontend application. Behind the scenes, Astro will automatically render the widgets used in the area through the widget components mapped previously.

To see more Astro template component implementations, explore the `./src/templates` folder of the `apostrophecms/astro-frontend` repository.

After defining all the necessary Astro template components, you have to list them in the mapping file specified in the aforementioned `templatesMapping` option. In this sample application, the `./src/templates/index.js` mapping file will contain:

```
// ./src/templates/index.js

import HomePage from './HomePage.astro';
import DefaultPage from './DefaultPage.astro';
import BlogIndexPage from './BlogIndexPage.astro';
import BlogShowPage from './BlogShowPage.astro';
import NotFoundPage from './NotFoundPage.astro';

const templateComponents = {
  '@apostrophecms/home-page': HomePage,
  'default-page': DefaultPage,
  '@apostrophecms/blog-page:index': BlogIndexPage,
  '@apostrophecms/blog-page:show': BlogShowPage,
  '@apostrophecms/page:notFound': NotFoundPage
};

export default templateComponents;
```

This file imports all the Astro template component files, associates them with the equivalent ApostropheCMS template in a mapping object, and exports it. 

For ordinary page templates, such as the home page or the “default” page type, just specify the name of the Apostrophe module. For special templates like `notFound` and for modules that serve more than one template, you have to specify the full name. 

For instance, the page type `@apostrophecms/blog-page` has an index template to display the main blog page and a `show` template to display a single blog post. If you omit the specific template name, `:page` is assumed.

To map the `404` page to a component, use `@apostrophecms/page:notFound`. Also, bear in mind that the `@apostrophecms/apostrophe-astro` library involves two additional template names that can be mapped to custom components:

- `apos-fetch-error`: Served when Apostrophe generates an [HTTP 5XX error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses). In this case, Astro will respond with the `500` status code.
- `apos-no-template`: Served when there is no mapping corresponding to the ApostropheCMS template.

**Handle Routing in Astro via ApostropheCMS**

In this setup, ApostropheCMS is responsible for managing URLs to content, including creating new content and pages on the fly. So, you will only need one top-level Astro page component matching all routes.

To define it, add a `[...slug].astro` dynamic route file in the `./src/pages` folder of your project as follows:

```
---
// ./src/pages/[...slug].astro

import aposPageFetch from '@apostrophecms/apostrophe-astro/lib/aposPageFetch.js';
import AposLayout from '@apostrophecms/apostrophe-astro/components/layouts/AposLayout.astro';
import AposTemplate from '@apostrophecms/apostrophe-astro/components/AposTemplate.astro';

const aposData = await aposPageFetch(Astro.request);
const bodyClass = `myclass`;

if (aposData.redirect) {
  return Astro.redirect(aposData.url, aposData.status);
}
if (aposData.notFound) {
  Astro.response.status = 404;
}
---
<AposLayout title={aposData.page?.title} {aposData} {bodyClass}>
    <Fragment slot="standardHead">
      <meta name="description" content={aposData.page?.seoDescription} />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta charset="UTF-8" />
    </Fragment>
    <AposTemplate {aposData} slot="main" />
</AposLayout>
```

The `aposPageFetch()` function from the `@apostrophecms/apostrophe-astro` library fetches the data for the current URL from ApostropheCMS. In detail, the `aposData` object will contain all of the information normally provided by `data` in an ApostropheCMS Nunjucks template. This includes:

- `page`: The HTML template document associated with the current URL.
- `piece`: The piece document, when visiting a "show page" of a particular piece page type.
- `pieces`: An array of pieces, when visiting an "index page" of a given piece page type.
- `user`: General information about the currently logged-in user.
- `global`: The ApostropheCMS global settings.
- `query`: The query parameters of the URL.

Any other custom data set by ApostropheCMS is also available here.

The `AposLayout` component finds the right Astro template component to render based on the template mapping defined earlier. It also automatically handles the switch between the editing UI when a user is logged in and the normal layout for visitors. `AposLayout` accepts four props:

- `aposData`: The data fetched from ApostropheCMS.
- `title`: The page title to set in the the `<title>` HTML tag.
- `lang`: The language code to set in the `<html>` `lang` attribute.
- `bodyClass`: The custom `class` attribute to add to the `<body>` element.

To override any aspect of the global layout, take advantage of the [named Astro slots](https://docs.astro.build/en/basics/astro-components/#named-slots) matching what [template blocks ApostropheCMS offers in Nunjucks](https://v3.docs.apostrophecms.org/guide/layout-template.html#layout-templates). In the example below, the `standardHead` `<Fragment />` component is used to add a slot at the very beginning of the `<head>` element of the page.

Wonderful! Your Astro frontend project for an ApostropheCMS backend is ready!

## Get Started With the ApostropheCMS Astro Application

Now that you know how to set up an Astro project and an ApostorpheCMS project that can communicate with each other, it is time to see the resulting application in action!

Here, we will assume that you cloned the two projects presented at the beginning of the article.

**Start the Projects**

To prevent other users from retrieving a lot of data from your ApostropheCMS backend without your permission, you must use the `APOS_EXTERNAL_FRONT_KEY` environment variable. Set that env to a secret value when running the Astro project and set the same variable to the same value when running the Apostrophe application.

Use the commands below to start your ApostropheCMS backend locally:

```
cd starter-kit-astro
npm install
export APOS_EXTERNAL_FRONT_KEY=<YOUR_SECRET>
npm run dev
```

By default, ApostropheCMS will start on port `3000`. Visit `http://localhost:3000` and you should see:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r6649si8s8ve6hm1zf9f.png)

Similarly, run your Astro frontend with:

```
cd astro-frontend
npm install
export APOS_EXTERNAL_FRONT_KEY=<YOUR_SECRET>
npm run dev
```

By default, Astro will start on port `4321`. Open `http://localhost:4321` in your browser and you will see:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjmlyfvfyc3wdzlok4g3.png)

If you forget to set the `APOS_EXTERNAL_FRONT_KEY` environment variable, you will receive the following error message:

```
[ERROR] APOS_EXTERNAL_FRONT_KEY environment variable must be set, here and in the Apostrophe app
```

And the error page below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zgy3wux0sykclpefbao5.png)

**Add an ApostropheCMS Admin User and Log In**

As mentioned on the Astro frontend home page, the next step is to log in as an administrator. If you have already set up an admin user in the ApsotropheCMS project, you can skip to the next step. 

Otherwise, run the following command inside the ApostropheCMS project folder to add an administrator user:

```
node app @apostrophecms/user:add myAdmin admin
```

Replace `myAdmin` with the name you want to give the user. During the process, you will be prompted for a password. Type it in and press ENTER.

**Access the ApostorpheCMS Editing UI in Astro**

Now, click on the login link or visit `http://localhost:4321/login`. Fill out the login form with your ApostropheCMS admin credentials:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f6o9y63cwnlp693983f.png)

Press the “Login” button, and you will get access to the editing UI:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o2gw5c4o0ptocb49ggz.png)

Click on the "Edit" button in the upper right corner to enter the ApostropheCMS in-context editing mode:

![](https://paper-attachments.dropboxusercontent.com/s_E4CF389D69550EA8FD834EC769D79DBAE457F954BB57B796C160DFA26014DA2E_1708527843104_1.gif)

Keep in mind that this is all happening in Astro, not in the ApostropheCMS application.

**Create a Blog Page**

The `starter-kit-astro` project is based on the ApostropheCMS `@apostrophecms/blog` module. This helps you create a blog in ApostropheCMS in just a few minutes. To understand how this module works and what it offers, [read our dedicated guide](https://apostrophecms.com/blog/how-to-build-a-blog-apostrophe).

To get started with the blog module, create a new page of type "Blog Page.” Click on the "Pages" option in the top left menu, then hit the "New Page” button:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o3ofymp5prkm4sb535o.png)

Fill out the ApostropheCMS page creation modal by selecting the “Blog Page” type, and click “Publish:”

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvudmj30esbp03d4kbkq.png)

Visit the `http://localhost:4321/blog` and you will now see:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s4mjkmmivswo51unq03.png)

This corresponds to the `./src/templates/BlogIndexPage.astro` template component in your Astro project. The page is currently empty as you still have to populate your blog with some posts. Learn how to do that in the next step!

**Populate Your Blog**

To create a new blog post, select “Blog post” in the top left menu and then click the "New Blog post” button. You will reach the following form:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4unoojbp70tk7p02bb4z.png)

Give your blog a title, a publication date, a slug, and write some content. Then, press "Publish.”

Add a few posts to your application and visit `http://localhost:4321/blog` again. This time, you will see:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zoesri1py9kydh4yfgeq.png)

Click on a blog post link and you will be redirected to the blog piece's show page:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pu72bjjasl6kifxmq8s7.png)

Et voilà! You just used the ApostropheCMS content editing features to build your Astro site!

## Next Steps

Part 1 of this two-article series ends here. Although the results achieved are pretty amazing, there is still a lot to be done. At the end of the day, the blog application is pretty ugly right now. In the next part, you will learn how to make your application more interactive and visually appealing using React components in Astro. 

Continue with the article “How to Integrate Astro With ApostropheCMS pt. 2” to complete the ApostropheCMS / Astro integration.