Learn how to build modern websites with a headless CMS architecture using ApostropheCMS as your backend and Astro for lightning-fast frontend rendering. This demo shows you the complete integration pattern with working examples you can use immediately.
This repository serves as both a learning resource and starter template for building your own ApostropheCMS + Astro projects.
This hybrid approach combines:
- Structured content management - ApostropheCMS provides an intuitive editing experience with in-context editing
- Modern frontend performance - Astro delivers optimal page load speeds with partial hydration
- Developer flexibility - Keep backend content modeling separate from frontend presentation
- Production-ready patterns - Demonstrates real-world integration including external API calls and blog functionality
Perfect for: Development teams evaluating headless CMS options, agencies building client sites, or developers learning modern web architecture patterns.
Page Types:
- Home page with customizable areas
- Default content page template
- Article index and show pages with working blog functionality
Widgets:
- Core content widgets (rich text, image, video, file)
- Layout widgets (layout, layout column) for structured page composition
- Marketing components (hero, button, card, price card)
- Article widget for content relationships
- GitHub PRs widget demonstrating external API integration
Additional Features:
- Component registry pattern for mapping backend modules to frontend templates
- Shared utilities for area configuration and link fields
- Complete development and deployment workflows
- Node.js v22 or later
- MongoDB v6.0 or later (setup guide)
First, fork the astro-public-demo repo (give it a star while you're there).
Then:
git clone <your-repo-url>
cd astro-public-demo
npm run install-allSet the environment variables in your terminal:
export APOS_EXTERNAL_FRONT_KEY=dev
APOS_ALLOWED_DOMAINSis not needed for local development — it defaults to**.apos.devfor ApostropheCMS hosting. Set it when self-hosting with a custom backend domain (see Deployment).
Then start both servers:
npm run devOr run them separately in two terminals:
# Terminal 1 - Backend (port 3000)
cd backend && npm run dev
# Terminal 2 - Frontend (port 4321)
cd frontend && npm run devVisit http://localhost:4321 to see the site.
Generate a fully static version of the site served from the root path (/).
1. Start the backend:
cd backend
npm run dev2. Build the static frontend (in a second terminal):
cd frontend
npm run build:static3. Preview the build:
cd frontend
npm run preview:staticOpen http://static.localhost:4000 to see the static version.
For production builds:
# Terminal 1 - Backend
cd backend
export NODE_ENV=production
APOS_EXTERNAL_FRONT_KEY=dev npm run serve
# Terminal 2 - Frontend
cd frontend
export NODE_ENV=production
npm run build:staticNote: You can change the
APOS_EXTERNAL_FRONT_KEYvalue for thebuild:staticcommand.
You can generate a fully static site and deploy it to GitHub Pages (or any static host that serves from a sub-path).
1. Start the backend with the prefix and base URL for your GitHub Pages site:
cd backend
export NODE_ENV=production
export APOS_PREFIX=/<your-repo>
export APOS_STATIC_BASE_URL=https://<your-github-user>.github.io
npm run serve:gh2. Build the static frontend (in a second terminal):
cd frontend
export NODE_ENV=production
export APOS_PREFIX=/<your-repo>
npm run build:ghThe output is in frontend/dist/ and ready to be served from /<your-repo>/.
For this repository, the commands are:
# Terminal 1 - Backend
cd backend
export NODE_ENV=production
export APOS_PREFIX=/astro-public-demo
export APOS_STATIC_BASE_URL=https://apostrophecms.github.io
npm run serve:gh
# Terminal 2 - Frontend
cd frontend
export NODE_ENV=production
export APOS_PREFIX=/astro-public-demo
npm run build:ghAlternatively, use the deploy script to build and push to GitHub Pages in one step (the backend must be running as described above):
./scripts/gh-deploy-staticThe script auto-detects <your-github-user> and <your-repo> from the origin remote, starts the build, and pushes to the gh-pages branch. Run ./scripts/gh-deploy-static --help for options like --dry-run and --no-build.
cd backend
node app @apostrophecms/user:add admin admin├── backend/ # ApostropheCMS headless CMS
│ ├── modules/ # Page types, pieces, and widgets
│ ├── lib/ # Shared utilities (area config, link fields)
│ └── app.js # Main configuration
├── frontend/ # Astro application
│ ├── src/
│ │ ├── pages/ # Single [...slug].astro catch-all route
│ │ ├── templates/ # Page type components
│ │ ├── widgets/ # Widget components
│ │ └── components/ # Reusable Astro components
│ └── astro.config.mjs
└── package.json # Root scripts for running both projects
- Backend (ApostropheCMS) defines content schemas, widgets, and page types
- Frontend (Astro) renders content using mapped components
- Bridge (
@apostrophecms/apostrophe-astro) connects them, enabling in-context editing
This pattern allows you to maintain a clean separation between content modeling and presentation while still providing editors with a seamless editing experience.
Templates and widgets are mapped by name in index files:
frontend/src/templates/index.js- Maps page type names to Astro componentsfrontend/src/widgets/index.js- Maps widget names to Astro components
Keys must match backend module names exactly (e.g., 'default-page', '@apostrophecms/rich-text').
- Create the widget module in
backend/modules/{widget-name}/index.js - Register it in
backend/app.js - Create the Astro component in
frontend/src/widgets/{WidgetName}.astro - Add the mapping in
frontend/src/widgets/index.js
- Create the page module in
backend/modules/{page-name}/index.js - Register it in
backend/app.jsand add to@apostrophecms/pagetypes - Create the template in
frontend/src/templates/{PageName}.astro - Add the mapping in
frontend/src/templates/index.js
---
import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro';
const { page } = Astro.props;
---
<AposArea area={page.main} />Zero-config deployment with automatic database provisioning, SSL, and asset optimization. Learn more
Deploy the backend and frontend separately:
Backend: Any Node.js host with MongoDB access (see hosting docs)
Frontend: Any SSR-capable host (Netlify, Vercel, Cloudflare Pages, etc.) with these environment variables set:
| Variable | Required | Description |
|---|---|---|
APOS_EXTERNAL_FRONT_KEY |
Yes | Shared secret between the Astro frontend and ApostropheCMS backend |
APOS_ALLOWED_DOMAINS |
Yes | Comma-separated list of backend hostname patterns Astro is allowed to proxy to. Wildcards are supported (e.g. mysite.apos.dev, **.example.com, or api.example.com,**.cdn.example.com). Defaults to **.apos.dev. |
Important: Production Security Configuration
Astro requires an allowedDomains entry in astro.config.mjs for certain
ApostropheCMS operations — including file uploads and logout — to work correctly
in production. Without it, those operations will silently fail with a 403.
This does not affect local development.
Add your ApostropheCMS backend domain to the security block in
frontend/astro.config.mjs:
export default defineConfig({
// ... other config
security: {
allowedDomains: [
{
hostname: 'your-apos-backend.com',
protocol: 'https'
}
]
}
});This tells Astro to trust X-Forwarded-Host headers from your backend, which
it uses to construct the request origin for CSRF validation. Setting
checkOrigin: false alone is not sufficient.
Requires
astro@5.14.2or later. Wildcard hostnames (e.g.*.yourdomain.com) are supported if your backend and frontend share a domain.
This demo focuses on core integration patterns. When you're ready to build a production project, the Astro Essentials Starter Kit provides a minimal foundation you can build your own design system on top of.
Need enterprise features like advanced permissions, automated translation, or document versioning? Contact us to learn about ApostropheCMS Pro.
- ApostropheCMS Documentation
- Astro Documentation
- apostrophe-astro Package
- ApostropheCMS + Astro Tutorial
- Discord Community
Built by the ApostropheCMS team. Star us on GitHub if this helps your project!