N
HeadlessCoreHeadless Core
Back to Home

Architecture

This document explains how HeadlessCore is structured, how data flows between WordPress and Next.js, and where each responsibility lives.

High-level design

HeadlessCore follows the decoupled (headless) CMS pattern:

  1. WordPress = content API + admin UI
  2. Next.js = public website + performance layer
  3. WPGraphQL = contract between them

WordPress never renders the public theme to visitors when Enable Headless Mode is on. Instead, template_redirect sends browsers to the Next.js URL.

Request lifecycle (visitor)

Browser → GET https://yoursite.com/blog/my-post
         │
         ▼
    Next.js (Vercel/Node)
         │
         ├─ Static HTML from ISR cache? → return immediately
         │
         └─ Cache miss / stale → fetch WPGraphQL
                │
                ▼
            WordPress /graphql
                │
                ▼
            JSON response → render React → cache for 60s

Request lifecycle (editor publishes)

Editor clicks "Update" in wp-admin
         │
         ▼
WordPress save_post hook
         │
         ▼
POST https://yoursite.com/api/revalidate
  { secret, path: "/blog/my-slug", tag: "post" }
         │
         ▼
Next.js revalidatePath + revalidateTag
         │
         ▼
Next visitor gets fresh content

WordPress theme layers

File / folder Responsibility
functions.php Settings page, core CPTs, GraphQL themeSettings, contact mutation, licensing, revalidation webhook
inc/extended-cpts.php Shop products, team, FAQ, pricing CPTs + GraphQL fields
inc/demo-importer.php One-click sample content
inc/admin-dashboard.php Dashboard health widget

Next.js layers

Path Responsibility
src/lib/graphql.ts All WPGraphQL fetch functions + mock fallback
src/lib/mockData.ts Offline demo data when WP is unreachable
src/lib/siteMode.ts Multipurpose mode config, sitemap routes
src/lib/blogSearch.ts Mock-side blog search/pagination logic
src/lib/postMapper.ts Maps raw GraphQL post nodes to BlogPost type
src/lib/config.ts Environment variable accessors
src/lib/seo.ts Metadata builders, JSON-LD
src/lib/rateLimit.ts In-memory rate limiter for API routes
src/app/page.tsx Homepage router — reads homeLayout from WP
src/app/[...slug]/page.tsx Catch-all for any WordPress page URI
src/components/BlockRenderer.tsx Gutenberg HTML enhancement

Caching strategy

Layer Mechanism TTL
Next.js ISR export const revalidate = 60 60 seconds
On-demand WordPress webhook → /api/revalidate Instant
GraphQL fetch tags fetch(..., { next: { tags: ["posts"] } }) Tag-based invalidation
WP object cache Standard WordPress (optional Redis) Host-dependent

Cache tags

Tag Invalidated when
theme-settings Any theme option saved
posts Post published/updated
post-{slug} Specific post
page-{uri} Specific page
services Service CPT changed
testimonials Testimonial CPT changed
headless-products Product CPT changed
team-members Team CPT changed
faqs FAQ CPT changed
pricing-plans Pricing CPT changed

Security boundaries

See the full Security Guide for production hardening and a parallel comparison with WP Engine + Faust.js.

Concern Implementation
WP admin Standard WordPress auth; not exposed on Next.js
Revalidation secret Shared secret in WP settings + Next.js env; constant-time compare
Preview secret Query param gate on /api/preview; constant-time compare
Draft content JWT bearer token server-side only
Rate limiting Per-IP on API routes; optional Upstash Redis for multi-instance
Contact form GraphQL mutation + Turnstile CAPTCHA + honeypot + WP-side rate limits
GraphQL introspection Disabled in production (WP_ENVIRONMENT_TYPE=production)
Security headers CSP, HSTS, X-Frame-Options on Next.js; headers on WP CMS

Mock fallback mode

If NEXT_PUBLIC_WORDPRESS_API_URL is unset or the GraphQL request fails, every function in graphql.ts returns data from mockData.ts. This lets you:

  • Develop UI without a running WordPress instance
  • Demo the product at conferences
  • Run CI builds without a database

A banner is not shown automatically — check the homepage connection status card in multi mode, or the WP dashboard widget.

Multipurpose routing

themeSettings.homeLayout controls what renders at /:

homeLayout → page.tsx switch → <BlogPage /> | <CorporatePage /> | …

Demo routes (/blog, /corporate, etc.) always work regardless of mode, so clients can preview all layouts before choosing one.

Comparison with WP Engine Atlas / Faust.js

See Security — HeadlessCore vs WP Engine for a full parallel comparison (security, features, cost).

Aspect HeadlessCore WP Engine Atlas
Hosting Self-hosted Managed platform
Cost Server only Platform subscription
WordPress Any host WP Engine required
Frontend Next.js (you own) Faust.js / Next.js
GraphQL WPGraphQL (standard) WPGraphQL + platform tooling
Revalidation Custom webhook Platform-integrated
Vendor lock-in None Platform-dependent

Extension points

  1. New CPT — Register in functions.php or inc/extended-cpts.php, add GraphQL fields, add fetcher in graphql.ts, add mock data
  2. New site mode — Add to siteMode.ts, create src/app/{mode}/page.tsx, add WP admin dropdown option
  3. New theme settingregister_setting() + register_graphql_field() on ThemeSettings
  4. New API route — Add under src/app/api/