OPEN TO FULL-TIME ROLES (Available to start immediately. Remote preferred worldwide.)// STACK:WORDPRESS & WOOCOMMERCEREACT & NEXT.JSTYPESCRIPTWEB PERFORMANCEAI INTEGRATIONSAUTOMATION (N8N, MAKE)// PROTOCOL: MCP · JSON-RPC 2.0// STATUS: PRODUCTION READY
OPEN TO FULL-TIME ROLES (Available to start immediately. Remote preferred worldwide.)// STACK:WORDPRESS & WOOCOMMERCEREACT & NEXT.JSTYPESCRIPTWEB PERFORMANCEAI INTEGRATIONSAUTOMATION (N8N, MAKE)// PROTOCOL: MCP · JSON-RPC 2.0// STATUS: PRODUCTION READY
HDRX
• 12 min read
AstroWordPressHeadless CMSOpen SourcePerformance

AstroPress: A Headless WordPress + Astro Starter With 0 KB Client JS

How AstroPress decouples WordPress as a pure editorial backend from an Astro static frontend — the content layer, the 4-tier SEO cascade, real-time draft preview, and the CI performance budgets that enforce it.

AstroPress is an open-source starter that decouples WordPress from page rendering: WordPress stays a pure editorial backend — Gutenberg, taxonomies, media library — while Astro queries the WordPress REST API entirely at build time and compiles static HTML/CSS for the edge, shipping 0 KB of client-side JavaScript by default on editorial pages. The project is on GitHub under the MIT license.

The problem it targets is a specific and common one: WordPress is genuinely good as a content-editing experience for non-technical authors, and genuinely bad as a page-rendering runtime once traffic or performance requirements get serious. Most “headless WordPress” writeups stop at “call the REST API from your frontend.” AstroPress goes further — it’s an opinionated, batteries-included stack with a real content-normalization layer, a companion WordPress plugin for authenticated draft preview, enforced performance budgets in CI, and a diagnostic tool that checks the whole pipeline end to end.

Why Decouple WordPress From Rendering at All

A conventional WordPress theme executes PHP on every request: bootstrap WordPress, run every active plugin’s hooks, query MySQL for the post and its metadata, render a template, and only then send HTML. Under load, or with a heavy plugin stack, that path is where most WordPress performance problems originate — not the frontend.

Headless WordPress removes PHP from the request path for content pages entirely. WordPress becomes a data source consumed at build time (or via on-demand SSR for the few things that genuinely need it, like draft preview); the actual page a visitor loads is static HTML served from the edge, with no database round-trip per request. The trade-off is real: you give up the “install a plugin, get a feature” ergonomics of a monolithic WordPress theme, in exchange for rendering performance that doesn’t degrade under traffic.

Architecture: Content Layer, Not Just an API Call

AstroPress’s docs/architecture.md documents the data flow as: WordPress → REST API (/wp-json/wp/v2/*) → a WordPress client (client.ts) → raw JSON → a content layer (src/lib/wordpress/) that normalizes the payload → typed, normalized data consumed by Astro routes and components → static HTML (or the on-demand preview route).

The content layer is the part that matters architecturally. Rather than letting WordPress’s REST response shape leak into every page component — field names, nested taxonomy objects, WordPress-specific quirks — the normalization layer isolates that entirely. Components consume a typed, project-defined shape; if WordPress’s API response changes or you swap a plugin that alters the payload, you fix it in one place instead of hunting through every template that touched post.acf.some_field directly.

The project also documents explicit JavaScript boundaries: client-side fetches or hydration are prohibited for content that’s known at build time — that’s what the content layer and static generation are for. Astro islands are permitted only for narrowly-scoped interactivity that genuinely can’t be static, like a client-side search box. This is the same discipline that keeps a “static-first” architecture from quietly regressing into a client-rendered app one convenient client:load at a time.

The 4-Tier SEO Cascade

Real WordPress sites accumulate SEO metadata from multiple, often conflicting sources: a dedicated SEO plugin, native WordPress fields, and site-wide defaults, not always kept in sync with each other. AstroPress’s README describes an “intelligent metadata cascade” that resolves this deterministically: Yoast SEO → Rank Math → native WP fields → site defaults, falling through to the next source only when the current one has nothing set, and feeding the result into Schema.org JSON-LD graphs (BlogPosting, BreadcrumbList, WebSite).

The practical benefit is that migrating between SEO plugins, or running a site that started with native WP fields and later added Yoast, doesn’t produce silently missing metadata — the cascade always resolves to something, in a predictable priority order, instead of requiring every template to special-case which plugin happens to be active.

Image Pipeline and Draft Preview

Remote media from WordPress is compiled through Astro’s astro:assets pipeline into responsive WebP/AVIF at build time, with explicit width and height attributes set so the browser can reserve layout space before the image loads — the direct mechanism for keeping Cumulative Layout Shift at zero, rather than relying on lazy-loading alone to mask the problem.

Static generation has one structural gap: editors can’t see unpublished drafts on a site that only builds from published content. AstroPress’s answer is a companion WordPress plugin, astropress-connector, that performs a tokenized handshake with an on-demand SSR route in Astro — a narrow, purpose-built exception to the static-by-default rule, scoped specifically to preview rather than left open as a general SSR escape hatch. An editor gets real-time preview of unpublished content without the project giving up its default of shipping publish-time static HTML for everything else.

Headless Doctor: Diagnosing the Full Pipeline

Headless setups fail in ways that are hard to localize — is it WordPress, the REST connector, permalinks, the preview plugin, or Astro’s build? AstroPress ships a diagnostic tool, run via npm run doctor (CLI) or the /doctor dashboard (web), that checks seven categories end to end: environment configuration, WordPress connectivity, REST endpoint availability, permalink structure, the connector plugin’s installation state, SEO plugin detection, and the draft-preview handshake.

This is the kind of tooling that’s easy to skip in a minimal headless-WordPress tutorial and expensive to be missing once something breaks in a real deployment — a diagnostic that tells you which of the seven moving parts failed saves the alternative of manually checking each one under time pressure.

Performance Budgets Enforced in CI, Not Just Documented

AstroPress declares performance budgets in a budget.json file and enforces them via npm run audit:perf in CI — per the README, blocking CSS bloat above roughly 25 KB and any accidental client-side JavaScript on editorial pages. The distinction that matters here: a performance budget that’s only a claim in a README is not a performance budget, it’s a hope. Enforcing it as a CI gate means a pull request that regresses the CSS payload or accidentally hydrates a component that should be static actually fails the build, instead of shipping and being caught (or not) later by whoever happens to run Lighthouse manually.

The project’s test suite backs this with 130+ tests via Vitest, per the README’s CLI reference table, covering the content-normalization and SEO-cascade logic that would otherwise be the most likely place for silent regressions.

Getting Started

The documented quickstart:

git clone https://github.com/hd-rx8/AstroPress-Headless-Starter.git
cd AstroPress-Headless-Starter
npm install
docker compose up -d   # WordPress 6 + MySQL 8.4, pre-seeded with demo content
npm run doctor         # verify the full pipeline before starting dev
npm run dev            # http://localhost:4321

WordPress admin is reachable at http://localhost:8080/wp-admin, and the diagnostics dashboard at /doctor. Configuration lives in .env, requiring WORDPRESS_URL, SITE_URL, and ASTROPRESS_PREVIEW_SECRET (the shared secret used by the draft-preview token handshake). The stack is built on Astro — currently Astro 7 per the project’s package.json — with Node 22.12 or later required.

When AstroPress Fits, and When It Doesn’t

Fits well:

  • Content-heavy sites (blogs, marketing sites, documentation) where non-technical editors need Gutenberg’s editing experience but the frontend needs to be fast and cheap to host.
  • Teams migrating an existing WordPress site to a static frontend without abandoning the CMS editors already know.
  • Projects where CI-enforced performance budgets are a real requirement, not a nice-to-have — the tooling exists specifically to make regressions visible before merge.

Doesn’t fit well:

  • Highly dynamic, per-user content (a logged-in dashboard, real-time data) — that’s a different rendering model than what static-first, build-time content generation is designed for.
  • Teams unwilling to run two systems (WordPress + a separate build/deploy pipeline for the Astro frontend) instead of a single monolithic WordPress install — the decoupling that buys the performance also adds an operational moving part.
  • Sites needing instant, un-cached content updates on every publish, faster than a rebuild cycle allows, without also wiring up incremental or on-demand regeneration.

Frequently Asked Questions

Does AstroPress require running WordPress and Astro as two separate deployments?

Yes — WordPress functions purely as the editorial backend and content API, while Astro is the build/deploy target that produces the static site. The Docker Compose setup runs both locally for development, but a production deployment typically keeps WordPress on its own host (or a managed WordPress provider) and deploys the Astro build output to a static/edge host separately.

How does AstroPress handle SEO plugin conflicts between Yoast and Rank Math?

Through the 4-tier metadata cascade: Yoast SEO is checked first, then Rank Math, then native WordPress fields, then site-wide defaults, with the first tier that has data set winning. This avoids the common failure mode of a template hardcoding which plugin’s fields to read and silently missing metadata if the site switches SEO plugins later.

Can editors preview unpublished WordPress drafts on a statically generated site?

Yes, through the astropress-connector WordPress plugin performing a tokenized handshake with a dedicated on-demand SSR route in Astro. This is the one deliberate exception to the project’s static-by-default rendering model, scoped specifically to draft preview rather than general dynamic rendering.

What does the “0 KB client JavaScript” claim actually mean?

It refers to editorial pages specifically — content rendered from WordPress data ships as static HTML/CSS with no client-side JavaScript bundle for that content. The project’s documented JavaScript boundaries still permit narrowly-scoped Astro islands (like a client-side search box) for genuine interactivity; the constraint is that build-time-known content never gets client-side hydration it doesn’t need.

How is this different from just calling the WordPress REST API from a Next.js or Astro app manually?

The difference is the surrounding system: a dedicated content-normalization layer that isolates WordPress’s REST payload shape from the rest of the codebase, a 4-tier SEO cascade instead of ad hoc metadata handling, a purpose-built draft-preview mechanism, CI-enforced performance budgets, and a diagnostic tool covering the full pipeline. A hand-rolled fetch() call to /wp-json/wp/v2/posts gets you data; it doesn’t get you any of that surrounding tooling by default.


I use the same static-first, performance-budget-enforced approach on this site — see Agent-Ready Portfolios for the human-plus-agent dual-layer architecture, and the WooCommerce TTFB guide if the site you’re decoupling from WordPress also runs WooCommerce. The AstroPress repo has the full source; if you’re evaluating it for a real migration, get in touch.

Questions or project ideas?

Reach out directly to discuss architecture, optimization, or AI agents.

Get in Touch →