How to Use WordPress as a Headless CMS with Astro

I get why using WordPress as a headless CMS feels like a big call. The pitch is a faster and safer front-end, but the bill is a second codebase, a deploy pipeline and editors who lose their Preview button. And now EmDash has arrived as a third option that changes the math again.

My advice is simple. I’d go headless with Astro when the front-end is the product, and I’d stay on traditional WordPress when it isn’t. I’d look at EmDash only for a new Astro site that can live without WordPress plugins.

I publish every article on this site through the WordPress REST API, and I run EmDash on 2 of my own sites, so the tests below are my own.

Headless WordPress setup with WordPress as the content back end and a separate front-end

For this update, I pointed a fresh Astro project at this site’s live API again. Two files of code built 21 static pages in about 2 seconds, and 301 pages in under 25 seconds, with no plugins on either side. That speed is still the whole pitch for headless WordPress.

EmDash is the newcomer. It’s a free, open-source CMS from Cloudflare, built as an Astro integration, and it adds an admin panel, a database and a media library to the Astro app itself.

It launched as a beta in April 2026 and reached its first stable release in September. My 2 EmDash sites are core-forms.com and the GyaanKit blog, and I build plugins for EmDash too, so weigh my take with that in mind.

Headless WordPress keeps the part WordPress is great at, which is managing content, and hands rendering to a front-end framework. React and Next.js still make sense for app-shaped projects. Gatsby no longer makes my list, and Astro is the default I reach for.

Here’s how the three setups compare side by side.

SetupWho renders pagesWhat Publish doesPluginsBest for
Traditional WordPressThe PHP theme, on each visit (a cache helps)Goes live instantlyEvery WordPress plugin and themeBlogs, stores and sites with several editors
Headless WordPress + AstroAstro, at build timeTriggers a rebuildOnly plugins that expose data through the APICustom front-ends and content shared across channels
EmDashAstro, on each requestGoes live instantly, no rebuildA small, young plugin catalogNew Astro sites with few plugin needs

What Headless WordPress Means

Headless WordPress is a split. WordPress keeps the admin, the editor, and the database, while a separate front-end fetches your content over an API and renders every page. The “head” that gets removed is the theme layer.

  • Traditional WordPress: CMS and front-end tightly integrated. One system does everything.
  • Headless WordPress: CMS and front-end decoupled, talking to each other through the REST API or GraphQL.
  • EmDash: CMS and front-end in one Astro app, with no API hop between them.

Why Teams Decouple

  • Flexibility: build any interface you can imagine, free of PHP and theme framework constraints.
  • Performance: a static front-end serves plain HTML, which beats even a well-cached PHP render.
  • Security: your public site becomes static files; the WordPress admin can hide on a separate domain.
  • Scalability: front-end and back-end scale independently, and static hosting scales almost for free.

How the Pieces Talk

  • Your WordPress install serves content as JSON, through the built-in REST API or the WPGraphQL plugin.
  • A front-end framework (Astro, Next.js, Nuxt, SvelteKit) fetches that JSON at build time or request time and renders it.

One system writes. The other renders. Everything else in this guide is plumbing between the two.

Where EmDash Fits

EmDash takes the opposite route. Instead of splitting WordPress in two, it adds a CMS to the front-end, so the admin, the database and the pages ship as one Astro app.

  • Runs on Cloudflare Workers with D1 and R2, or on any Node.js server with SQLite.
  • Stores rich text as Portable Text, a structured JSON format, instead of HTML.
  • Queries content live through Astro’s Live Collections, so publishing needs no rebuild.
  • Signs editors in with passkeys, and ships a CLI and a built-in MCP server for scripts and AI tools.
  • License: MIT, so it’s free to use and self-host.

That removes the API hop, and most of the plumbing this guide covers goes with it. The price is every WordPress plugin and theme you leave behind.

Diagram comparing three setups: traditional WordPress renders pages from MySQL on every visit, headless WordPress sends JSON to an Astro build that rebuilds on publish, and EmDash keeps the admin, database and Astro routes in one app with no rebuild

The WordPress Side of the Setup

You don’t need a special install to use WordPress as a headless CMS. You need a normal install, ideally tucked onto a subdomain like cms.example.com, and the REST API it already ships with.

  • Install WordPress normally, preferably on a subdomain so the front-end can own the main domain.
  • Confirm the API works by opening /wp-json/wp/v2/posts in a browser. JSON means you’re live.
  • Point a front-end at it. That’s genuinely the whole architecture.

Tools You Need

  • WordPress REST API: built into core, nothing to install.
  • WPGraphQL (optional): free, and the most actively maintained headless WordPress plugin.
  • Advanced Custom Fields (optional): for structured content beyond posts and pages.

Hosting That Fits the Split

  • Managed WordPress host for the back-end: Cloudways, Kinsta or WP Engine. The admin still needs PHP and a database; only the public site goes static. If none of those fit, I’ve compared Cloudways alternatives and WP Engine alternatives.
  • Static host for the front-end: Netlify, Vercel or Cloudflare. All three rebuild your site on a webhook and serve it from a CDN.
  • EmDash needs no separate back-end host. It deploys as one app to Cloudflare Workers, with D1 for content and R2 for media, or to a Node server with SQLite. Sandboxed plugins need the Workers Paid plan ($5 a month minimum), while plugins you install as regular site dependencies run without it.

I’ve covered deploying an Astro site to Cloudflare Workers on the free plan in my guide to designing websites with free tools.


Astro Is My Front-End Pick

If I were building a headless WordPress front-end today, I’d start with Astro. The reasoning is simple: most content sites are mostly HTML, and Astro is the one major framework built around that fact.

It ships zero JavaScript by default and renders everything to static HTML at build time. Interactive pieces become “islands” that hydrate individually while the rest of the page stays inert and fast.

It’s also a project in motion. Astro 5 arrived in December 2024, Astro 6 in March 2026 and Astro 7 in June 2026. That release pace matters when you’re betting a production site on a framework; you’ll see why when we get to Gatsby.

The Build I Ran

This isn’t a hypothetical example. Here is the entire front-end I pointed at this site’s API. Two files.

src/pages/index.astro lists the latest posts:

---
const res = await fetch(
  'https://gauravtiwari.org/wp-json/wp/v2/posts?per_page=20&_fields=id,slug,title,excerpt,date'
);
const posts = await res.json();
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Latest posts</title>
  </head>
  <body>
    <h1>Latest posts</h1>
    <ul>
      {posts.map((post) => (
        <li>
          <a href={`/posts/${post.slug}/`} set:html={post.title.rendered} />
        </li>
      ))}
    </ul>
  </body>
</html>

src/pages/posts/[slug].astro builds one static page per post:

---
export async function getStaticPaths() {
  const res = await fetch(
    'https://gauravtiwari.org/wp-json/wp/v2/posts?per_page=20&_fields=slug,title,content,date'
  );
  const posts = await res.json();
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}
const { post } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title set:html={post.title.rendered} />
  </head>
  <body>
    <article>
      <h1 set:html={post.title.rendered} />
      <div set:html={post.content.rendered} />
    </article>
  </body>
</html>

Then npm run build. On Astro 7.4, the result was 21 pages in about 2 seconds across 3 runs (2.00 to 2.23 seconds). The pages came live from this site’s REST API as plain HTML, with no client-side JavaScript at all.

Fast.

Notice one quiet win in there: the fetch() runs at build time, on your machine or your CI server. The browser never makes a cross-origin request, so the CORS headaches that haunt client-side headless setups never happen.

Notice also the _fields parameter, which tells WordPress to return only the fields you name. I use it in my own publishing scripts; it’s the difference between a lean response and downloading the kitchen sink.

Astro’s documentation keeps a dedicated Headless WordPress guide, and unlike a lot of framework docs, it’s current.

Islands for the Interactive Bits

A static site still needs the occasional live component: a search box, a comment form, a currency converter. In Astro you write that one component in React, Vue, Svelte, or plain Astro, and hydrate just that component:

<SearchBox client:load />

The rest of the page ships as HTML. That’s the architecture most WordPress content sites actually need: 95 percent document, 5 percent app.

The Same Page in EmDash

In EmDash you don’t call an API at all. The posts live in the app’s own database, so the same latest-posts page queries them directly:

---
import { getEmDashCollection } from "emdash";

const { entries: posts } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
  limit: 20,
});
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Latest posts</title>
  </head>
  <body>
    <h1>Latest posts</h1>
    <ul>
      {posts.map((post) => (
        <li>
          <a href={`/posts/${post.id}/`}>{post.data.title}</a>
        </li>
      ))}
    </ul>
  </body>
</html>

I ran this exact page on a local EmDash 1.3.1 site. Two differences matter:

  • The page renders on each request, so a post you publish shows up without a rebuild.
  • post.id is the slug, and the title is a plain string under post.data.title. WordPress wraps the same title in a { rendered } object.

Where the Other Frameworks Fit

Astro is my default, not a religion. Two of these alternatives are healthy choices. One is not.

Next.js for App-Shaped Sites

If your “site” is really an application, with logged-in users, personalization, or heavy interactivity on every page, Next.js earns its complexity. It’s on version 16 as I write this, and its incremental static regeneration lets you cache WordPress content and refresh it on a timer:

const res = await fetch('https://example.com/wp-json/wp/v2/posts', {
  next: { revalidate: 60 }, // refetch at most once per minute
});
const posts = await res.json();

Content stays fresh without rebuilding the site. For a WordPress front-end with app features, that’s the right tool. For a blog, it’s more machinery than the job needs.

Nuxt and SvelteKit

Both are healthy, actively developed, and perfectly capable of consuming the WordPress REST API or WPGraphQL with the same fetch patterns you’ll see below. My guidance: pick Nuxt if your team already lives in Vue, SvelteKit if it lives in Svelte. Neither will beat Astro at the pure content-site job, and neither will let you down.

EmDash only belongs in this decision if your front-end is Astro. It’s an Astro integration, so a Next.js, Nuxt or SvelteKit team that wants WordPress’s editor should stay on headless WordPress.

Gatsby

Earlier versions of this guide recommended Gatsby for static WordPress sites. I can’t do that anymore.

Gatsby’s last major release shipped in November 2022, and what ships now is occasional maintenance patches. Existing Gatsby sites keep working, but I wouldn’t start a new headless WordPress project on a framework that has stopped moving. Everything Gatsby was best at, Astro now does with less ceremony.


Connecting to the WordPress REST API

The WordPress REST API is the interface between WordPress and everything else: your front-end, your scripts, your tools. It ships with core, and it writes as well as it reads.

This very update went live through it; my publishing pipeline authenticates against the same API this article teaches and writes the post over HTTP. I’ve also documented how AI tools manage my links through a REST API, because once content is addressable as JSON, automation gets easy.

I keep the endpoints and WP-CLI commands I use most in my WordPress developer cheat sheet.

The Endpoints

Every WordPress site exposes the REST API at the same address:

https://example.com/wp-json/wp/v2/

Open it in a browser. If you see JSON, your API is live and you’ve done all the WordPress-side setup a read-only headless site requires.

Fetching Posts

fetch('https://example.com/wp-json/wp/v2/posts?_fields=id,slug,title,excerpt')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(posts => {
    console.log(posts);
    // Render your posts here
  })
  .catch(error => {
    console.error('Error fetching posts:', error);
  });

Three things worth knowing about this request:

  • _fields trims the response to only the fields you name. Use it everywhere.
  • per_page goes up to 100; after that you paginate with page=2 and the X-WP-TotalPages response header.
  • Titles and content come back as { rendered: "..." } objects, so it’s post.title.rendered, not post.title.

Single Posts, Categories and Tags

Single items live at predictable addresses, and taxonomies filter with query parameters:

// One post by ID
fetch('https://example.com/wp-json/wp/v2/posts/123')
  .then(res => res.json())
  .then(post => console.log(post.title.rendered));

// One post by slug (returns an array)
fetch('https://example.com/wp-json/wp/v2/posts?slug=my-post-slug')
  .then(res => res.json())
  .then(([post]) => console.log(post.title.rendered));

// All categories, then posts in category 5
fetch('https://example.com/wp-json/wp/v2/categories')
  .then(res => res.json())
  .then(categories => console.log(categories));

fetch('https://example.com/wp-json/wp/v2/posts?categories=5')
  .then(res => res.json())
  .then(posts => console.log(posts));

The by-slug form is the one headless front-ends actually use, because your routes carry slugs, not database IDs.

Authentication Without Plugins

You don’t need a plugin for API authentication. WordPress has shipped application passwords in core since version 5.6. Go to your profile in wp-admin, generate one under “Application Passwords,” and send it as HTTP Basic auth:

fetch('https://example.com/wp-json/wp/v2/posts?status=draft', {
  headers: {
    'Authorization': 'Basic ' + btoa('yourusername:xxxx xxxx xxxx xxxx xxxx xxxx')
  }
})
  .then(res => res.json())
  .then(drafts => console.log(drafts));

That’s exactly how my own publishing scripts authenticate, over HTTPS only, with a password I can revoke without touching my real login. If you need token-based flows instead, the JWT Authentication plugin is still available, though its latest release is tested only up to WordPress 6.9. WPGraphQL JWT covers the same job for GraphQL.

EmDash handles this differently. Editors sign in with passkeys, and scripts log in through its CLI with npx emdash login. I publish the GyaanKit blog from Markdown files with one script on top of that CLI, which is the same pattern as this site’s REST pipeline.


Using GraphQL With WPGraphQL

REST answers with everything an endpoint has; GraphQL answers with exactly what you asked for, from a single endpoint. On a small blog the difference is cosmetic. On a build that touches posts, authors, categories, and custom fields for hundreds of pages, it adds up: fewer requests, smaller payloads, and queries that read like the data you want.

Setting Up WPGraphQL

WPGraphQL GraphiQL IDE running inside the WordPress admin

GraphQL isn’t built into WordPress, but the setup is two steps:

  • Install and activate the free WPGraphQL plugin. Your site immediately exposes a /graphql endpoint.
  • Open the bundled GraphiQL IDE from your wp-admin toolbar and test queries in the browser before you write any code.

Your First Queries

A GraphQL query states what you want and nothing else. This fetches the latest five posts:

query {
  posts(first: 5) {
    nodes {
      id
      title
      date
      excerpt
    }
  }
}

And from JavaScript, it’s one POST request. No client library required:

const query = `
  query {
    posts(first: 5) {
      nodes {
        id
        title
        link
      }
    }
  }
`;

fetch('https://example.com/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query })
})
  .then(res => res.json())
  .then(result => {
    console.log(result.data.posts.nodes);
  })
  .catch(error => {
    console.error('GraphQL fetch error:', error);
  });

That same fetch drops straight into an Astro frontmatter block and runs at build time. Nested data is where GraphQL starts paying rent; this pulls posts with their authors and categories in one round trip:

query {
  posts(first: 5) {
    nodes {
      title
      author {
        node {
          name
        }
      }
      categories {
        nodes {
          name
        }
      }
    }
  }
}

In REST, that’s three separate requests and some client-side stitching. In GraphQL, it’s one shape.

Custom Fields With ACF

If you model content with Advanced Custom Fields, install WPGraphQL for ACF and your field groups appear in the schema:

query {
  posts {
    nodes {
      title
      acfPostFields {
        customFieldName
        featuredImage {
          sourceUrl
        }
      }
    }
  }
}

Version 3.0 of WPGraphQL for ACF changed one default. ACF options pages now stay out of the schema until you turn on show_in_graphql for them. Post fields like the ones above aren’t affected.

React and Apollo

If your front-end is React and you’re querying at runtime rather than build time, Apollo Client handles caching and loading states for you:

import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_POSTS = gql`
  query {
    posts(first: 5) {
      nodes {
        id
        title
      }
    }
  }
`;

const Posts = () => {
  const { loading, error, data } = useQuery(GET_POSTS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.posts.nodes.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

export default Posts;

For static builds, skip Apollo entirely. Plain fetch at build time is simpler and disappears from your bundle.

What EmDash Does Instead

EmDash has no GraphQL layer, and it doesn’t need one. Each collection is a typed table in the database, and npx emdash types generates TypeScript types from the live schema. You get the same “only what I asked for” benefit from typed queries inside the app, with no endpoint in between.


Common Headless WordPress Problems

Going headless trades one set of problems for another. These are the ones you’ll actually hit, and what fixes them.

CORS Errors

When a browser on yoursite.com fetches data from cms.yoursite.com, the browser blocks the cross-origin request unless WordPress explicitly allows it:

Access to fetch at 'https://cms.yoursite.com/wp-json/wp/v2/posts'
from origin 'https://yoursite.com' has been blocked by CORS policy.

Fix it in .htaccess on the WordPress server, and allow your front-end’s origin specifically rather than *:

<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "https://yoursite.com"
  Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
  Header set Access-Control-Allow-Headers "Content-Type, Authorization"
</IfModule>

Better fix: fetch at build time, like the Astro example above. Server-to-server requests don’t involve a browser, so CORS never applies.

SEO and Missing Meta

Purely client-rendered React or Vue front-ends serve search engines an empty shell, and pages get indexed without titles or descriptions.

The fix is structural: render HTML on the server or at build time, which Astro, Next.js, Nuxt, and SvelteKit all do. Then pull your titles, descriptions, and canonical URLs from WordPress through the API and render them into each page’s head. That way the SEO plugin your editors already use stays the single source of truth.

Slow API Responses

Every API call is a network round trip into PHP and MySQL, and an unoptimized WordPress back-end will make even a static build crawl. Three fixes, in order of impact:

  • Prefetch at build time so visitors never wait on the API at all.
  • Add object caching (Redis or Memcached) on the WordPress server.
  • Put Cloudflare or another CDN in front of the API for cacheable GET requests.

Routing Mismatches

WordPress permalinks and framework routes are two different systems that must agree, or you’ll ship broken URLs and lose rankings on a migration. The rule: make your framework’s dynamic routes mirror your existing permalink structure exactly, slug for slug.

The [slug].astro file earlier does precisely this, and Next.js dynamic segments work the same way. Audit your top URLs before launch and 301 anything you genuinely must change.

Editors Lose Previews

This is the cost nobody budgets for. The classic “Preview” button renders through your theme, and you just deleted the theme. Writers notice immediately.

WPGraphQL supports draft previews you can wire into your front-end, and Netlify and Vercel both offer preview deployments per branch. It works, but it’s real engineering effort, and if your site has multiple non-technical editors, weigh this section heavily before going headless.

Stale Content After Publishing

With a static front-end, hitting Publish in WordPress changes the database, not your site. You need one of two mechanisms:

  • Rebuild on publish: a webhook from WordPress pings your host’s deploy hook, and Netlify, Vercel, or Cloudflare rebuilds the site.
  • Timed revalidation: Next.js-style ISR refetches content on an interval without a full rebuild.

My own test puts numbers on this. 21 pages build in about 2 seconds, and 301 pages took 18 to 23 seconds, most of it spent fetching full post content from the API. “Rebuild everything on publish” stays a sane strategy far longer than most tutorials admit.

Your Plugins Stay Behind

A headless front-end ignores everything a WordPress plugin prints into a theme, for example:

  • forms and popups
  • product boxes and related posts
  • schema and other markup an SEO plugin adds to the page

Only plugins that expose their data through the API come along, and you rebuild the front-end part yourself. Forms are usually the first thing to break, so plan a form endpoint or service your front-end can post to.

Analytics needs the same rethink. A script-based tracker such as GT Analytics, which I built on Cloudflare, works the same on a static front-end as it does on WordPress.

How EmDash Handles These

EmDash removes some of these problems and keeps others. This is how the same list looks there:

ProblemHeadless WordPressEmDash
CORS errorsFetch at build time or allow your front-end’s originAdmin, API and pages share one origin
SEO metaPull it from your SEO plugin through the APIBuilt-in SEO fields and a getSeoMeta() helper
Slow APIBuild-time fetch, object caching and a CDNNo API hop; pages query D1 or SQLite directly
RoutingMirror your WordPress permalinks in the routesThe importer keeps slugs; your routes still have to match
PreviewsWire them up with WPGraphQL and preview deploysSigned preview links through your real routes
Stale contentRebuild on publish or revalidate on a timerLive queries, no rebuild
PluginsOnly API-aware plugins carry overA small plugin catalog; few WordPress plugins have ports

Moving WordPress Content to EmDash

If you’re weighing EmDash, the real question is what survives the move. I pointed EmDash’s importer at this site, then imported 6 posts that use the blocks I rely on most.

EmDash WordPress importer detecting gauravtiwari.org with 1,910 posts, 201 pages and 23,452 media items, and a table showing that a WXR file cannot import widgets or ACF fields and brings Rank Math data only as raw meta, while the EmDash Exporter plugin imports all of them

The importer checks your site through the same /wp-json/ endpoint this guide uses, and it counted every public post, page and media file here. In EmDash 1.3.1, that check is all the REST route does. The import itself takes one of two routes:

  • A WXR export file from Tools > Export in WordPress. It brings posts, pages, media, taxonomies, menus and drafts, but no widgets or ACF fields, and Rank Math or Yoast data arrives as raw meta.
  • The EmDash Exporter plugin, installed on your WordPress site. It covers everything in that table, including ACF fields and SEO data.

I used a WXR export of 6 posts. This is what came through:

  • Slugs and publish dates survived exactly, so your URLs don’t have to change.
  • Core blocks became native EmDash content: paragraphs, headings, lists, tables, images, quotes and all 15 code blocks in this guide.
  • Custom blocks didn’t. All 6 product review boxes, every key takeaway callout and the table of contents arrived as raw block markup that renders nothing on the front-end.
  • Shortcodes came through as literal text. My [year] headings printed the brackets, and [data_table] and [cf_form] did the same.

Those gaps are why I ported my own plugins to EmDash. Dynamic Dates resolves the same date shortcodes, so [year] renders again after a migration.

Content Blocks rebuilds review boxes, FAQs, callouts and comparisons as native EmDash blocks, and Core Forms and Lead Capture cover forms. All 5 are free and open source on my EmDash plugins page.

Plan for the rest yourself. Theme templates, page-builder layouts and PHP logic don’t migrate, and every dynamic block needs an EmDash equivalent before the post looks right again.


Should You Go Headless?

This site is not headless, and I’m the person who just showed you a two-second headless build of it.

I stay on traditional WordPress because my publishing workflow leans on things the theme renders for me: SEO meta, product boxes, forms, structured blocks. My editors (mostly me) get instant previews.

And with proper caching and a CDN, the performance argument for going headless mostly evaporates for a content site. Going headless would mean rebuilding all of that plumbing for a gain my readers would barely measure.

So I see three sensible choices, and the right one depends on where your ceiling is.

Stay on Traditional WordPress

Traditional WordPress should stay when you run a straightforward blog or a standard eCommerce setup, or when editors depend on plugins, blocks and instant previews. A fast theme with good caching solves those problems with a tenth of the moving parts.

Go Headless With Astro

Headless WordPress earns its cost when:

  • The front-end is the product: custom interfaces, app-like interactions, design that no theme should constrain.
  • Multiple channels read one content source: website, mobile app, digital signage, all from the same WordPress.
  • Your team lives in JavaScript and treats WordPress purely as an editorial back office.

Choose EmDash

EmDash makes sense for a new content site when:

  • your front-end is already Astro, or you’re happy to make it Astro
  • the site needs few plugins, and a young plugin catalog doesn’t scare you
  • you want plugins that run with only the permissions they declare
  • you’re comfortable on Cloudflare, or on a Node server you manage

EmDash should be skipped when you depend on WooCommerce or a heavy plugin stack, when several editors need mature workflows, or when you can’t bet on a 1.x project. Its store features are minimal, and a large WordPress site means rebuilding every custom block.

For most content sites, traditional WordPress is still the right answer. Headless WordPress and EmDash solve specific problems, and a blog that’s merely slow has a caching problem.

FAQs on Headless WordPress

Not in the usual sense. EmDash runs inside your Astro app, so the admin, the database and the pages ship together. It has a REST API, but most EmDash sites query content directly from Astro.

Final Remarks

So don’t treat WordPress as a headless CMS as a maturity ladder you’re supposed to climb. It’s a trade: you gain a front-end with no ceilings, and you take on a second codebase, a preview problem and a deploy pipeline. EmDash trades differently, giving you one app and live content in exchange for WordPress’s plugins and themes.

If the ceiling is your actual problem, I’d start with Astro and point it at the API you already have. You’ll have a working site before your coffee cools. If you’re starting fresh and can live without WordPress plugins, I’d try EmDash on a small site first.

And if you’d rather make the move with help, I build headless WordPress front-ends, WordPress API integrations and website migrations, including moves to Astro or EmDash.

Tell Google you want more of this.

Add Gaurav Tiwari as a preferred source

One tap, and this site shows up more often in your own Top Stories, AI Overviews and AI Mode. Remove it any time.

Disclaimer: This site is reader-supported. If you buy through some links, I may earn a small commission at no extra cost to you. I only recommend tools I trust and would use myself. Your support helps keep gauravtiwari.org free and focused on real-world advice. Thanks. - Gaurav Tiwari