How the ApostropheCMS + Astro integration works

A short introduction to the architecture of ApostropheCMS headless project with Astro frontends tied together with our `apostrophe-astro` integration package.

    • Beginner
    • 7 min read
    • Astro
    • Last Updated August 3, 2026

What you'll learn

  • How responsibility is split between your Apostrophe project and your Astro project, and why that split exists

  • What the @apostrophecms/apostrophe-astro bridge package actually does

  • How the template and widget registries connect Apostrophe content to Astro components — and the naming rules that trip people up

  • How area fields and the AposArea component let editors manage content without touching your templates

  • Where to go for setup steps, full configuration options, and deeper architectural reference

Why a separate frontend at all?

If you're coming to ApostropheCMS from its traditional Nunjucks or JSX templating, the Astro integration is a different way of building the same site: Apostrophe still owns content, but Astro owns rendering.

That's a meaningful shift, not just a swap of template languages. It means your team can use Astro's component model, its framework interoperability (React, Vue, Svelte, or plain Astro components), and its build tooling, while keeping the ApostropheCMS content modeling, schema system, and in-context editing UI exactly as they are.

Two projects, one site

When you adopt this integration, you end up with two codebases working together as one application:

  • An Apostrophe project — defines every page type, piece type, and widget type, along with their schemas. This is where content structure lives.
  • An Astro project — owns every template and every component that turns that content into HTML. It never defines a content shape; it only renders what ApostropheCMS hands it.

The convention is to keep both in backend and frontend subdirectories of a single repository, which keeps versioning and deployment aligned.

This split has a practical payoff: a frontend developer can build and rework components without touching CMS configuration, and someone shaping content architecture can add or change fields without breaking a template. The two layers move independently as long as the contract between them — the registries described below — stays accurate.

  • What lives in Apostrophe

    • Page types and piece types
    • Widget schemas and field definitions
    • The admin editing UI
    • Content storage and the REST API that serves it
  • What lives in Astro

    • Every .astro template and component
    • Frontend logic, styling, and any framework-specific UI (React/Vue/Svelte)
    • The single catch-all route that requests content from Apostrophe
  • Apostrophe owns content structure and editing.

  • The bridge package fetches and hands off content.

  • Astro owns every template and how content renders.

What the bridge package actually does

The @apostrophecms/apostrophe-astro package is what makes these two projects feel like one site rather than two services awkwardly stitched together. It's installed in your Astro project (not your Apostrophe project), and it provides four things:

A data-fetching helper. aposPageFetch runs in your single [...slug].astro route and authenticates with the Apostrophe backend, fetching the page document — or piece, for a piece-type show page — along with all of its widget data in one request. Authentication between the two projects is handled by a shared secret value, so the Apostrophe backend knows the request is legitimately coming from your Astro frontend rather than the public internet.

Layout and template components. AposLayout provides the page shell — head tags, body structure, and the switch between the normal site and the in-context editing experience for logged-in users. AposTemplate looks up and renders the correct Astro component for whatever page or piece was fetched, based on the template registry.

The AposArea component. This is what lets an editable area of content — built from any number of widgets — render correctly in Astro while preserving Apostrophe's add/remove/reorder editing controls. More on this below.

The in-context editing overlay. This is what makes it possible for an editor to click directly on a live Astro-rendered page and edit content, the same experience they'd have on a traditional Apostrophe site.

How Apostrophe content finds its Astro component

This is the part of the integration most worth understanding before you start building, because it's also the most common source of "why isn't my widget showing up" bugs.

There's no automatic, filename-based discovery connecting Apostrophe content to Astro components. Instead, two explicit registry files do the matching:

  • A template registry maps page type and piece type names to Astro page components
  • A widget registry maps widget type names to Astro widget components

The matching rule is strict: the key in each registry has to match the type name exactly as ApostropheCMS stores it in the database — not necessarily the name of the backend module file.

For widgets, that means stripping -widget from the module name and including the @apostrophecms/ namespace prefix for core widgets:

You might assumeWhat's actually needed
'rich-text': RichTextWidget'@apostrophecms/rich-text': RichTextWidget
'button-widget': ButtonWidget'button': ButtonWidget

For page and piece templates, a module that serves more than one template — like the ApostropheCMS blog, which has both a listing page and an individual post page — uses a colon suffix: '@apostrophecms/blog-page:index' and '@apostrophecms/blog-page:show'. If you create your own piece type later, follow the same 'your-piece-page:index' / 'your-piece-page:show' pattern.

A missing or mismatched template registry key (page type or piece type) appears to fail harder, since there's no fallback component to render in its place. See the architecture quick-reference for exact registry file locations and setup steps.

Area fields: the editor-facing layer

An area field is what gives editors the ability to add, remove, and reorder widgets on a page without any developer involvement. The split of responsibility here mirrors the project split described above:

  • The area's definition — which widgets are allowed, in what configuration — lives entirely in the Apostrophe backend schema.
  • The Astro template's job is just to hand that populated area data to AposArea and let it render. It doesn't need to know what's inside.

This means a content architect can change which widgets are available in a given area, in the backend schema, without anyone touching the Astro side. AposArea also handles nesting — an area can live inside a widget's own fields, which is how layout-style, multi-column widgets work.

Conventions worth knowing before you start

A few patterns show up consistently across the integration and are worth internalizing early:

  • The underscore prefix. Any field name starting with _ (like _image or _author) is a relationship field that Apostrophe resolves at request time. These are always returned as arrays, even when the schema limits the relationship to one item — so post._author?.[0]?.title is the standard access pattern, not post._author.title. For images specifically, that same [0] step is as far as you go by hand: widget._image?.[0] gets you the image object, which you then hand to the helper functions above rather than reaching further into it yourself.
  • Global Styles vs. Widget Styles. These are complementary, not competing systems. Global Styles set site-wide design tokens through the admin UI. Widget Styles are per-instance controls declared on a specific widget, letting an editor adjust one placement of a widget without touching code.
  • REST API availability isn't strategy-specific. If you're evaluating this integration against Apostrophe's traditional rendering, note that the REST API ApostropheCMS exposes is available regardless of which rendering approach a project uses — it's not something you only get with a headless or hybrid setup like this one.

Key takeaways

  • The Astro integration splits responsibility cleanly: Apostrophe owns content and editing, Astro owns rendering — connected by the @apostrophecms/apostrophe-astro bridge package.

  • The bridge package provides a data-fetching helper, layout and template components, the AposArea component, and the in-context editing overlay.

  • Two explicit registry files — one for templates, one for widgets — connect Apostrophe content types to Astro components. Key names must match exactly, including the @apostrophecms/ prefix and stripped widget suffix.

  • Registry mismatches behave differently depending on type: a missing widget key logs an error and produces no markup, while a missing template key fails harder.

  • Area fields keep content structure in the backend schema; Astro templates just render what's handed to them via AposArea.

  • Always use the bridge package's image helper functions rather than navigating the attachment object manually.

  • Editors always work in SSR mode — that's what enables in-context editing. Static output is a publishing option, not a separate editorial workflow: the published site can be deployed as SSR or static, but content editing always happens in SSR.