Headless Drupal Architecture: Reference Diagram and Component Guide
A reference architecture for headless Drupal — backend, API layer, frontend, preview flow, auth, caching, and deploy. What each component does and how they fit together.
A headless Drupal architecture isn’t one thing — it’s a stack of 6-8 components that work together. When teams ship broken headless sites, it’s almost always because they built the obvious components (Drupal + React frontend) and skipped the less-obvious ones (preview, caching, asset proxying). This guide walks through the full component set, what each does, and the decisions to make for each.
If you’re newer to the concept, start with What is Decoupled Drupal?. For the specific React implementation, see Headless Drupal with React.
The reference architecture
A typical production headless Drupal stack has these components:
┌──────────────────┐
│ Editors │
│ (admin UI) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Drupal │
│ (CMS backend) │
│ cms.domain.com │
└────────┬─────────┘
│
JSON:API / GraphQL
│
▼
┌────────────────────────────────────────┐
│ API Gateway / CDN │
│ (cache, rate limit) │
└────────┬───────────────────────┬───────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Frontend │ │ Preview │
│ (Next.js) │ │ Tunnel │
│ domain.com │ │ │
└────────┬─────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ Visitors │
└──────────────────┘
Let’s walk through each layer.
Layer 1: The Drupal backend
This is your CMS. It runs Drupal core plus the modules you need for your content model. In headless mode, the frontend theme barely matters — nobody ever sees it except admins.
Key modules:
jsonapi(core) — exposes every content entity as a REST API by default. Production-ready, well-documented, spec-compliant.jsonapi_extras(contrib) — fine-grained control over what JSON:API exposes. You’ll want this to hide user fields, admin content, or internal entities from the API.next(contrib) — handles preview token issuance and webhook-based revalidation. Essential for any real preview workflow.pathauto+redirect(contrib) — clean URLs and redirect management. Both travel with the content in JSON:API responses.metatag(contrib) — SEO metadata in the content model. The frontend reads these fields and renders them as<meta>tags.media+media_library(core) — modern media management. The frontend reads media entities and renders them.
Hosting options: Pantheon, Platform.sh, Acquia Cloud, or self-hosted Docker. All work fine headless. The Drupal backend doesn’t need to be fast — it’s an API, not a user-facing site — so you can host on cheaper infrastructure than you might for a traditional Drupal site.
Layer 2: The API layer
Drupal itself is the API, but there are important configuration decisions:
REST or GraphQL?
- JSON:API (REST) is built into Drupal core, mature, and well-documented. Every entity is automatically exposed. This is what we default to.
- GraphQL requires the
graphqlcontrib module and schema definition work. Gives you more flexibility in the query shape but adds complexity. Use if you specifically need GraphQL.
What to expose:
- Strip out internal entities. Hide
userdata beyond thenamefield. Hide admin-only content types. - Control which fields are included by default. Large text fields can bloat API responses — mark them as excluded unless explicitly requested.
Rate limiting:
- Put rate limiting in front of the Drupal API. The most common way is via a CDN (Cloudflare, Fastly, CloudFront) with rate-limit rules. This protects Drupal from abusive traffic — bots, scrapers, or a misbehaving frontend build.
Layer 3: The frontend
This is the app visitors actually see. In our stack, it’s almost always Next.js (App Router), sometimes Astro, occasionally Vite + React.
Responsibilities:
- Routing. Map URLs to content. This is usually a catch-all route that looks up the Drupal node by path alias.
- Rendering. Take the JSON from Drupal and render it as HTML. Component-based rendering works well for structured content (one React component per Paragraph type, for example).
- Client interactivity. Anything dynamic — search, filters, forms, chatbots — lives here. The Drupal theme can’t do this well; the React frontend can.
- Caching. Use ISR (Next.js) or build-time rendering (Astro) to serve cached HTML instead of hitting Drupal on every request.
- Metadata. Read Drupal’s
metatagoutput and render as<head>tags.
Hosting options: Vercel (easy, fast, tight Next.js integration), Netlify (fine), self-hosted Docker (more work but cheaper at scale).
Layer 4: Preview
Preview is the component everyone forgets and everyone regrets forgetting. It works like this:
- Editor clicks “Preview” on a draft in Drupal.
- Drupal generates a signed preview token (JWT or similar).
- Drupal redirects the editor to the frontend with the token in the URL.
- Frontend validates the token, sets a preview cookie, and fetches the draft version of the content from Drupal instead of the published version.
- Editor sees the draft rendered on the real frontend.
The next contrib module handles the Drupal side automatically. The frontend side is 50-100 lines of code in Next.js using draftMode().
Test this early. Test it with multiple editors. Test it with content that has references to other drafts. This is where projects go sideways.
Layer 5: Revalidation
Cached frontend pages need to update when content changes. This is a two-part flow:
- Drupal fires a webhook on node save (via
nextcontrib or a custom hook_entity_presave). - Frontend receives the webhook at an endpoint like
/api/revalidate, validates a shared secret, and tells the framework to purge its cache for the affected page.
In Next.js 15, this is revalidateTag() with cache tags on your fetch calls. The result: editors publish in Drupal, and the public site updates within 10-30 seconds without a full rebuild.
For static-site builds (Astro, Next.js static export), revalidation triggers a rebuild instead. The tradeoff: simpler architecture, slower publishing.
Layer 6: Authentication (if needed)
If your site has logged-in users — customer accounts, member portals, paywall — you need to decide where auth lives:
- Drupal owns auth. Users register and log in via Drupal. The frontend calls Drupal’s auth endpoints to sign in and stores a session token. Drupal manages password reset, email verification, etc.
- Separate auth provider. Users authenticate via Auth0, Clerk, Supabase Auth, Firebase Auth. Drupal trusts the external provider via JWT verification. Drupal stores user-specific data but doesn’t handle credentials.
Pick based on existing infrastructure: if you already have an auth provider, use it. If you’re greenfield, Drupal auth is simpler to set up but less feature-rich than dedicated auth providers.
Layer 7: Asset delivery
Files and images in Drupal are served from the Drupal backend by default — URLs like cms.yoursite.com/sites/default/files/image.jpg. In a headless setup, you don’t want this: every image request hits Drupal, defeats your CDN, and exposes your backend domain publicly.
Fix: proxy or rewrite asset URLs so they serve from the frontend domain.
- CDN rewrite. Cloudflare, Fastly, or Vercel rewrites all requests for
/files/*to fetch from Drupal but cache aggressively. - Next.js rewrites. In
next.config.js, rewrite/sites/default/files/*to proxy through Next.js with long cache headers. - Pre-download assets. For static builds, download all media during the build and serve from the static deploy. Works for small sites; impractical for large ones.
Don’t skip this. It’s boring plumbing but it’s critical for real-world performance and security.
Layer 8: Observability
Two apps means two sets of errors. Wire up:
- Drupal error logging. Standard — New Relic, Sentry, or just watchtower logs.
- Frontend error tracking. Sentry or similar on the Next.js app.
- API call logging. Every frontend → Drupal call. Log request path, response time, and status. This catches API contract drift early (the frontend expects a field Drupal no longer returns).
- Build/deploy alerts. CI failures on either side should page someone.
Key decisions you’ll make
When scoping a headless Drupal build, these are the decisions that actually matter:
- REST (JSON:API) or GraphQL? — REST is simpler and the default. Pick GraphQL only if you have a specific reason.
- Next.js or Astro? — Next.js for interactive apps. Astro for content-heavy marketing sites. Both work.
- ISR or static build? — ISR for frequently updated content (news, blogs). Static builds for slower-changing content.
- Drupal auth or external provider? — Drupal if simple, external if you already have one or need features.
- Hosting for Drupal? — Pantheon/Platform.sh for ease, self-hosted for cost control at scale.
- Hosting for frontend? — Vercel for Next.js unless cost is extreme.
Each of these has right answers depending on your context. Don’t cargo-cult someone else’s stack — think about what fits your content, team, and budget.
TL;DR
Headless Drupal architecture is a stack of 8 components: Drupal backend, API layer, frontend, preview, revalidation, auth, asset delivery, and observability. The obvious layers (Drupal + frontend) are well-understood. The less-obvious ones (preview, revalidation, asset proxying) are where projects fail. Plan for all eight from the start, not just the first two.
We design and build headless Drupal architectures for production use. If you want a reference architecture tailored to your specific content and traffic patterns — let’s talk.
Related reading: