@astroblocks/astro-blocks
v2.0.0
Published
Block-based content CMS integration for Astro with admin panel, page builder, menus, SEO and cache invalidation.
Maintainers
Readme
Why AstroBlocks
- Edit pages in
/cmswithout adding a database. - Keep full control over the rendered HTML by using your own Astro components.
- Define blocks with a small, explicit schema contract.
- Store content in
data/*.jsonand uploads inpublic/uploads/. - Generate
sitemap-index.xmlandrobots.txtfrom the same content source. - Keep consumer imports explicit and type-safe.
Block Editor
The page editor is the core of AstroBlocks. It is designed as a compact block builder so content, SEO and structure can be managed from a single workflow.
- Block-first editing: pages are built by stacking your own Astro components as CMS blocks.
- Compact builder UI: block cards show the most relevant context first and keep advanced editing one step away.
- SEO and content together: title, slug, indexability and SEO metadata live in the same editing surface.
- Ordering without friction: blocks can be reordered, duplicated and removed directly from the editor.
Requirements
| Dependency | Version |
| --- | --- |
| Node.js | 18+ |
| Astro | 6+ |
| Adapter | @astrojs/node 10+ |
AstroBlocks alpha defaults to SSR public pages + Astro experimental cache. Use output: 'static' plus a server adapter so /cms, /cms/api, /robots.txt, /sitemap-index.xml and CMS-managed public pages can run dynamically.
Install
From npm
npm install @astroblocks/astro-blocks
npm install @astrojs/nodeFrom a local tarball
Use this when you want to validate a locally built package:
npm install /absolute/path/to/astroblocks-astro-blocks-<version>.tgzThe tarball flow is documented in LOCAL_PACKAGE_TESTING.md.
Recommended Imports
Keep imports split by responsibility:
import astroBlocks from '@astroblocks/astro-blocks';
import { defineBlockSchema } from '@astroblocks/astro-blocks/contract';
import { getConfig } from '@astroblocks/astro-blocks/getConfig';
import { getI18nMeta } from '@astroblocks/astro-blocks/getI18nMeta';
import { getLanguages } from '@astroblocks/astro-blocks/getLanguages';
import { getMenu } from '@astroblocks/astro-blocks/getMenu';@astroblocks/astro-blocksis the Astro integration entrypoint.@astroblocks/astro-blocks/contractis the public block-schema contract.@astroblocks/astro-blocks/getConfigreads CMS parameters fromdata/configs.jsonat runtime.@astroblocks/astro-blocks/getI18nMetabuildshreflang,html langand OpenGraph locale metadata from AstroBlocks i18n context.@astroblocks/astro-blocks/getLanguagesreads configured content languages for locale switchers.@astroblocks/astro-blocks/getMenuis the runtime helper for reading menu items inside your site.
Quick Start
1. Configure Astro
import { defineConfig, memoryCache } from 'astro/config';
import node from '@astrojs/node';
import astroBlocks from '@astroblocks/astro-blocks';
import { schema as heroSchema } from './src/components/Hero.schema.ts';
export default defineConfig({
output: 'static',
adapter: node({ mode: 'standalone' }),
experimental: {
cache: {
provider: memoryCache(),
},
},
integrations: [
astroBlocks({
layoutPath: './src/layouts/Layout.astro',
blocks: [heroSchema],
}),
],
});2. Define a block component
---
interface Props {
title: string;
subtitle?: string;
}
const { title, subtitle } = Astro.props;
---
<section>
<h1>{title}</h1>
{subtitle && <p>{subtitle}</p>}
</section>3. Define its schema
import { defineBlockSchema } from '@astroblocks/astro-blocks/contract';
export const schema = defineBlockSchema(
{
name: 'Hero',
icon: 'Layout',
items: {
title: { type: 'string', label: 'Title', required: true },
subtitle: { type: 'text', label: 'Subtitle' },
},
},
new URL('./Hero.astro', import.meta.url).href
);Array fields are also supported for repeatable content:
items: {
tags: {
type: 'array',
label: 'Tags',
minItems: 1,
maxItems: 6,
item: { type: 'string', label: 'Tag' },
},
faqs: {
type: 'array',
label: 'FAQs',
item: {
type: 'object',
label: 'FAQ',
summaryField: 'question',
fields: {
question: { type: 'string', label: 'Question', required: true },
answer: { type: 'text', label: 'Answer', required: true },
},
},
},
}4. Provide a layout for CMS-rendered pages
Your layout receives these props:
| Prop | Meaning |
| --- | --- |
| title | Final page title |
| description | Final meta description |
| canonical | Canonical URL |
| noindex | Whether the page is non-indexable |
| site | Data from data/site.json |
| seo | Final SEO object, including absolute image when present |
| i18n | i18n context for the current page (locale, defaultLocale, alternates) |
Example:
---
import { getMenu } from '@astroblocks/astro-blocks/getMenu';
import { getI18nMeta } from '@astroblocks/astro-blocks/getI18nMeta';
const { title, description, canonical, noindex, seo, site, i18n } = Astro.props;
const menu = await getMenu('main');
const i18nMeta = getI18nMeta(i18n, { baseUrl: site?.baseUrl });
---
<html lang={i18nMeta?.htmlLang || 'en'}>
<head>
<title>{title}</title>
{description && <meta name="description" content={description} />}
{canonical && <link rel="canonical" href={canonical} />}
{noindex && <meta name="robots" content={seo?.nofollow ? 'noindex, nofollow' : 'noindex'} />}
{seo?.image && <meta property="og:image" content={seo.image} />}
{i18nMeta?.alternates.map((entry) => (
<link rel="alternate" hreflang={entry.hrefLang} href={entry.href} />
))}
{i18nMeta?.ogLocale && <meta property="og:locale" content={i18nMeta.ogLocale} />}
{i18nMeta?.ogLocaleAlternate.map((entry) => (
<meta property="og:locale:alternate" content={entry} />
))}
</head>
<body>
<nav>
{menu.map((item) => <a href={item.path}>{item.name}</a>)}
</nav>
<slot />
</body>
</html>In SSR mode (
publicRendering: 'server'), AstroBlocks can useAccept-Languageon/to redirect to a non-default enabled locale when available. Locale preference is then persisted with a cookie so users can switch language and keep their chosen locale.
Data Model
AstroBlocks creates and reads these files in the consumer project root:
| Path | Purpose |
| --- | --- |
| data/pages.json | Pages, slug, status, blocks, indexable, SEO |
| data/site.json | Site name, base URL, favicon, logo, colors, default SEO |
| data/menus.json | Menus and nested menu items |
| data/redirects.json | Manual redirect rules (from, to, 301/302, enabled) |
| data/configs.json | Global key/value parameters consumable from code (key, value, description) |
| data/languages.json | Content languages (code, label, enabled, isDefault) |
| data/users.json | CMS users |
| public/uploads/ | Uploaded files |
You can version these files in your project repository if that fits your workflow.
CMS Routes
| Route | Purpose |
| --- | --- |
| /cms | Dashboard |
| /cms/pages | Pages |
| /cms/redirects | Redirect rules |
| /cms/configs | Global parameters |
| /cms/menus | Menus |
| /cms/settings | Site settings |
| /cms/users | Users |
| /cms/languages | Content languages |
| /cms/cache | Invalidate AstroBlocks cache |
API routes are available under /cms/api/*.
Menus In Your Site
---
import { getMenu } from '@astroblocks/astro-blocks/getMenu';
const mainMenu = await getMenu('main', { locale: 'es' });
---
<nav>
{mainMenu.map((item) => (
<a href={item.path}>{item.name}</a>
))}
</nav>Returned menu items have this shape:
type MenuItem = {
name: string;
path: string;
children?: MenuItem[];
};Languages In Your Site
---
import { getLanguages } from '@astroblocks/astro-blocks/getLanguages';
const { languages, defaultLocale } = await getLanguages();
---
<nav>
{languages.map((language) => (
<a href={language.code === defaultLocale ? '/' : `/${language.code}`}>
{language.label}
</a>
))}
</nav>Config Parameters In Your Site
---
import { getConfig, getConfigMap } from '@astroblocks/astro-blocks/getConfig';
const mapsKey = await getConfig('GOOGLE_MAPS_API_KEY');
const allConfigs = await getConfigMap();
---getConfig(key)matches keys case-insensitively and returnsstring | undefined.getConfigMap()returns every configured key/value pair as an object.- In SSR mode, updates from
/cms/configsare available after save + cache invalidation. - In
publicRendering: 'static', values are fixed at build time until the next rebuild.
Plugin Options
| Option | Description |
| --- | --- |
| layoutPath | Path to the Astro layout used when AstroBlocks renders a page |
| blocks | Array of block schemas imported from your .schema.ts files |
| publicRendering | 'server' by default in alpha. Use 'static' to opt back into prerendered public pages |
| cache | Cache behavior for SSR public pages. Enabled by default in alpha when the consumer configures an Astro cache provider |
| i18n.routingStrategy | Public routing contract for localized paths ('path-prefix' in this alpha) |
Cache Provider
AstroBlocks does not configure Astro's cache provider for you. The consumer project must opt into Astro's experimental cache explicitly:
import { defineConfig, memoryCache } from 'astro/config';
export default defineConfig({
experimental: {
cache: {
provider: memoryCache(),
},
},
});Without a provider, AstroBlocks will keep serving pages in SSR mode, but caching and invalidation will be inactive.
Static Opt-Out
If you want the public site to stay prerendered:
astroBlocks({
layoutPath: './src/layouts/Layout.astro',
blocks: [heroSchema],
publicRendering: 'static',
});Redirect rules are SSR-only in this alpha MVP. When
publicRendering: 'static', redirects configured in/cms/redirectsare not applied.
Using with AI Tools
@astroblocks/astro-blocks ships a consumer-facing AI context file (AGENTS.consumer.md) inside the npm tarball. AI coding assistants (Claude, Copilot, Cursor, Windsurf, etc.) can read this file to understand the integration API, block development patterns, admin routes, and environment variables without you having to explain them manually.
One-line setup:
npx astro-blocks init-aiThis command detects your project's AGENTS.md or CLAUDE.md (creating AGENTS.md if neither exists) and appends a reference to the consumer context file. The reference points to the installed package:
node_modules/@astroblocks/astro-blocks/AGENTS.consumer.mdThe context file is auto-versioned with the installed package — when you upgrade @astroblocks/astro-blocks, re-run npx astro-blocks init-ai to refresh the reference.
Use --copy to embed the full content inline instead of a reference link:
npx astro-blocks init-ai --copyConsumer Troubleshooting
Content changes do not appear on the public site
CMS-managed public pages are served in SSR by default in alpha. If changes do not appear:
- make sure the page is
published - make sure your project is using the AstroBlocks catch-all route and not a conflicting file in
src/pages/ - make sure your server adapter is configured correctly
- make sure Astro experimental cache is configured if you expect cache invalidation to work
In development, Astro exposes the cache API but does not cache real responses. Validate cache behavior in a built project or preview-like environment.
Regenerate site runs a fresh build artifact, but it is not required to see content changes during development.
The CMS routes do not work
Check all of these:
- you are using Astro 6+
- you have a server adapter configured
output: 'static'is enabled- the integration is included in
astro.config.*
My home page is not coming from the CMS
If your project already has src/pages/index.astro, Astro may serve that file instead of the CMS home page.
The layout receives a relative SEO image
AstroBlocks already converts relative seo.image values to absolute URLs before passing them to your layout. Use seo.image directly for og:image and twitter:image.
I want to validate a local build before publishing
Use the tarball flow documented in LOCAL_PACKAGE_TESTING.md.
For Maintainers
This README is intentionally consumer-focused.
If you are working on AstroBlocks itself, use:
- DEVELOPING.md for build, workspace, playground and release workflow
- AGENTS.md for repository-specific implementation rules
