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:
- An ApostropheCMS project: This is where you define your page types, widget types, and other content types with their schemas and other customizations.
- 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 ofapostrophe
. - 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 behttp://localhost:3000
. You can override this option through theAPOS_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 the500
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 customclass
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.