▶ Watch the walkthrough video: Manage content in Drupal, render with Astro, and deploy the public site as static files.
Status: Alpha / Developer Preview
Works end-to-end locally. Not yet tested across all OS/DDEV configurations.
Drupal Astro Kit is a static-first starter kit for developers who want Drupal's content modeling with a modern Astro frontend.
It turns Drupal 11 into a local content source and uses Astro to generate a fully static site. The production site is pre-rendered HTML that can be served from static hosting or a CDN, with no runtime CMS dependency for public page delivery.
Built for developers who want to keep Drupal where it shines: content management, structured data, and editorial workflow.
Core (Static-first)
- Astro Static Frontend — Pre-rendered pages, built locally, deployed to Cloudflare Pages
- Local Drupal 11 CMS — Fully managed inside DDEV
- Instant Bootstrap — One interactive CLI creates both Drupal and Astro projects
- Type-Safe API Access — jsona + drupal-jsonapi-params
- Simple Build Pipeline — Build locally, deploy static output
- Clean Routing — Drupal aliases mapped to Astro routes
- Zero Runtime Dependencies — Production site does not require Drupal at all
- Features
- Prerequisites
- Quick Start
- Local Development Flow
- Build & Deployment
- Publishing Workflow
- Architecture
- Project Structure
- Known Limitations
- Troubleshooting
This kit assumes:
- Drupal lives locally
- Astro fetches content at build time only
- Cloudflare Pages serves a static site
- No SSR dependency on Drupal in production
The interactive CLI:
- Creates a Drupal 11 project under DDEV
- Installs JSON:API, CORS configuration, and a starter content type
- Enables Drupal Linkset and seeds starter
main+footermenus - Can optionally install Media-based image handling with JSON:API image style URLs
- Creates an Astro project
- Wires Drupal → Astro environment variables
- Generates example routes that map Drupal aliases into Astro pages
Astro replaces Twig entirely.
Use React/Svelte/Solid/Vue islands if you want interactivity.
- Drupal → content
- Astro → presentation
- Cloudflare Pages → hosting
| Tool | Version | Install |
|---|---|---|
| Node.js | 20+ | brew install node@20 |
| DDEV | Latest | brew install ddev/ddev/ddev |
| Docker | Latest | brew install --cask docker |
| Composer | Latest | brew install composer |
| Cloudflare Account | Free | https://dash.cloudflare.com (deployment only) |
Important: In static mode, Drupal does not need to be hosted anywhere. It only needs to run locally when building the Astro site.
- Clone this kit and initialize your project
git clone https://github.com/rovo79/Drupal_Astro_Kit.git my-project
cd my-project
git remote remove originAdd your own GitHub repo:
git remote add origin https://github.com/your-user/my-project.git- Run the setup
chmod +x setup.sh
./setup.shThis will:
- Create drupal-backend/ in DDEV
- Create astro-frontend/
- Configure .env
- Map Drupal’s JSON:API into Astro
- Generate working SSG routes:
src/pages/index.astro+src/pages/[...slug].astro
- Starter content and menus are provisioned automatically
The setup flow applies Drupal recipes and seeds starter navigation:
- Pages:
/home,/about,/contact mainmenu:Home,About,Contactfootermenu:About,Contact
It also validates the required endpoint:
http://<project>.ddev.site/system/menu/main/linkset
Optional reseed command (safe to rerun):
./scripts/seed-content.shThis ensures published Basic pages and menu links exist and keeps Linkset enabled.
Optional setup capability:
- Media images: adds a Drupal Image media type, a
field_hero_imageMedia reference on starter page models, and JSON:API image style URLs for Astro build-time rendering
- Launch local development
# Start Drupal
cd drupal-backend && ddev start && ddev launch
# Start Astro
# When setup finishes, you are asked “Start Astro dev now in this terminal?”
# Say yes to reuse this terminal, or decline and run `cd astro-frontend && npm run dev` later.You now have:
- Drupal: http://my-project.ddev.site
- Astro: http://localhost:4321
You maintain content in Drupal locally.
Astro reads Drupal during npm run dev and npm run build:
- Page content and aliases from JSON:API
- Optional page hero images from Media references and JSON:API image style URLs
- Navigation menus from Linkset (
/system/menu/<menu>/linkset)
The deployed static site does not fetch Drupal at runtime.
Drupal (local)
↓ JSON:API + Linkset
Astro build
↓
Static HTML in dist/
↓
Cloudflare Pages Hosting
- Editing an existing Basic page only requires saving in Drupal and refreshing the browser while
npm run devis running; the dev server pulls the latest JSON:API data on navigation. - Editing a Drupal menu label only requires saving in Drupal and refreshing the browser while
npm run devis running; the dev server fetches fresh Linkset menu data per request. - Creating a new route or alias (new page or a different path) needs a fresh build (
npm run build) so Astro can regenerate the static HTML that includes the new route.
For the full publishing workflow and why rebuild-to-publish is intentional, see docs/publishing.md.
There is no Drupal runtime dependency in production.
Drupal decides which page is the “front page” via path aliases. Many sites keep the homepage at /home, while the public URL you care about is /.
Set HOMEPAGE_ALIAS in your .env file (default: /home). The Astro homepage route (src/pages/index.astro) will render:
- a Drupal page with alias
/if one exists, otherwise - the Drupal page matching
HOMEPAGE_ALIAS
That means your homepage content is authored once in Drupal (usually at /home) and appears at both / and /home in Astro.
For this starter kit, Astro should usually compose landing pages directly from Drupal JSON:API entity queries, not from Drupal Views.
Use direct JSON:API queries when:
- Astro owns the page layout and section ordering
- The data maps directly to content types or other entities
- You want explicit frontend-side normalization and predictable contracts
Use a View-backed JSON endpoint only when Drupal should own the feed logic, such as editor-managed curated feeds or unusually complex shared queries.
Search is a separate concern: direct JSON:API filtering is fine for simple browse flows, but faceted search usually deserves a dedicated Search API-style endpoint later.
- Cloudflare Account: Sign up at dash.cloudflare.com
- API Token: Create one with "Cloudflare Pages" permissions
- Account ID: Found in your Cloudflare dashboard URL
Add these to your .env file:
CLOUDFLARE_API_TOKEN=your-api-token-here
CLOUDFLARE_ACCOUNT_ID=your-account-id-hereBefore building, make sure your local Drupal is running:
cd drupal-backend && ddev startThen build the Astro site:
cd astro-frontend
npm run buildThis will:
- Connect to your local Drupal's JSON:API
- Fetch all published pages and menu Linksets
- Generate static HTML in
dist/
Option 1: Using the deploy script (recommended)
./scripts/deploy-frontend.shThis builds and deploys in one command.
Option 2: Manual deployment with Wrangler
cd astro-frontend
npm run build
npx wrangler pages deploy ./dist --project-name=my-projectOption 3: Cloudflare Dashboard (drag-and-drop)
- Go to Cloudflare Dashboard → Pages
- Click "Create a project" → "Direct Upload"
- Drag the
astro-frontend/distfolder
On first deploy, you may need to create the Pages project:
npx wrangler pages project create my-projectThen run the deploy script again.
To update content:
- Edit pages in Drupal at
http://my-project.ddev.site/admin/content - Run
./scripts/deploy-frontend.shto rebuild and deploy
Your changes will be live in seconds!
Drupal responsibilities
- WYSIWYG content
- Fields, media, menus
- JSON:API output (content + aliases)
- Linkset output (menu trees)
- URL aliases (Pathauto)
Astro responsibilities
- Fetch pages from Drupal JSON:API and menus from Drupal Linkset at build time
- Build static HTML from templates
- Handle routing using Drupal aliases
- Add interactivity using islands if desired
Cloudflare responsibilities
- Serve static site globally
- Serve static output via Pages CDN
The setup script generates something like:
Files:
src/pages/index.astrosrc/pages/[...slug].astro
---
import { getAllPages, aliasToSlug } from '../lib/drupal';
export async function getStaticPaths() {
const pages = await getAllPages();
return pages
.filter((page) => page.path?.alias && page.path.alias !== '/')
.map((page) => ({
params: { slug: aliasToSlug(page.path!.alias)! },
props: { page },
}));
}
const { page } = Astro.props;
---
<h1>{page.title}</h1>
<article set:html={page.body?.processed ?? ''} />This means:
- Astro builds /about, /company/team, etc. based on Drupal aliases
- No fetch at runtime
Note:
drupal-backend/andastro-frontend/are created by./setup.shand are gitignored. They do not exist in a fresh clone.
my-project/
├── astro-frontend/ # Astro SSG frontend (gitignored — created by setup.sh)
│ ├── src/
│ ├── astro.config.mjs
│ └── package.json
├── drupal-backend/ # Drupal 11 CMS under DDEV (gitignored — created by setup.sh)
│ ├── .ddev/
│ └── web/
├── setup/ # Interactive Ink-based installer
│ ├── cli.js
│ └── ui.js
├── templates/ # Source templates copied into astro-frontend/ during setup
├── scripts/ # Utility scripts (deploy, seed content)
├── setup.sh # Bootstrap script
├── .env # Created during setup (gitignored)
└── README.md # You are here
- Local-only Drupal: Drupal runs in DDEV on your machine. There is no hosted CMS option in V1. Every build requires your local Drupal to be running.
- No automated CI/CD out of the box: CI for this local-first project requires a Drupal instance reachable from the CI runner, which is not included by default.
- No test suite: The setup flow is validated manually. There are no automated unit or integration tests.
- macOS-first: Primarily tested on macOS with DDEV. Linux should work. Windows/WSL2 is untested.
- Static-only: Astro runs in
output: 'static'mode. There is no server-side rendering, no Cloudflare Workers runtime, no edge rendering. This is by design. - New routes require a rebuild: Adding a new page or alias in Drupal requires running
npm run buildagain — the Astro dev server does not pick up new routes dynamically. - Single content type in base recipe: The starter kit provisions Basic Pages only. Additional content types require custom Drupal recipes.
Astro cannot reach Drupal during build
Ensure DDEV is running:
cd drupal-backend
ddev startVisit http://my-project.ddev.site/jsonapi to confirm.
Cloudflare build fails
Cloudflare cannot reach your local Drupal. You must build locally, then deploy static output.
Aliases not appearing in JSON:API
This kit expects published Basic pages to have a URL alias (e.g. /about). The Astro routes are generated from path.alias returned by JSON:API (not from /node/123 internal URLs).
Contributions are welcome! Please read CONTRIBUTING.md before submitting issues or pull requests. Use the GitHub issue templates for bug reports and setup feedback.
The tooling and templates in this repository are MIT licensed — see LICENSE.
Drupal recipe packages under setup/drupal-recipes/ follow GPL-2.0-or-later, as required by the Drupal ecosystem. See each recipe's composer.json for details.