How to Integrate Astro With ApostropheCMS pt. 1

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.

Apostrophe recently announced the integration for the Astro web framework, 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 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" 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.

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.

The easiest way to achieve the Astro integration is through the ApostropheCMS 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:

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.

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 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 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:

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 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.

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

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. 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, 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. 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 matching what template blocks ApostropheCMS offers in Nunjucks. 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 ApostropheCMS 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:

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:

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:

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 ApostropheCMS 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:

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

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

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.

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:

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

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

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:

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:

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

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 this point, the blog application is not pretty. In the next part, you will learn how to make your application more interactive and visually appealing using React components in Astro.