Why headless WordPress still matters
Most WordPress installations are tightly coupled to their theme and presentation layer. You update content in the admin, it renders directly to your frontend. That coupling works fine for traditional blogs, but it breaks down when your editorial team needs a mobile app, a custom dashboard, or multiple branded experiences from the same content source. Headless WordPress: a practical guide for developers addresses exactly this problem by decoupling content management from presentation. Once you separate your WordPress backend from your frontend rendering layer, you gain the flexibility to build modern UIs with Next.js, Astro, mobile apps, or anything else while keeping your editorial workflow intact.
We've seen this architecture work well across trading platforms, SaaS products, and e-commerce sites where the marketing team needs to manage editorial content independently from the engineering team's frontend iterations. Let's walk through how to actually build this, from API selection through authentication and content preview.
Choosing between REST API and WPGraphQL
WordPress includes a REST API out of the box. It's REST-based, stateless, and relatively straightforward to consume. Every post, page, custom post type, and custom field can be queried via standard HTTP endpoints. For simple content delivery, this is often enough.
WPGraphQL is a community-maintained plugin that adds GraphQL to WordPress. Instead of multiple REST endpoints, you get a single query language where the frontend asks for exactly what it needs. No over-fetching, no under-fetching, and better developer experience when you have complex content relationships.
Here's a practical comparison. With the REST API, getting a post with its featured image and author requires two calls:
// REST API call 1: Get the post
GET /wp-json/wp/v2/posts/42
// Returns the featured_media ID, but not the image URL
{
"id": 42,
"title": "My Article",
"featured_media": 123,
"author": 5
}
// REST API call 2: Get the featured image
GET /wp-json/wp/v2/media/123
// REST API call 3: Get the author
GET /wp-json/wp/v2/users/5Three calls for what should logically be one request. With WPGraphQL, you get it in one:
query GetPostWithAuthorAndImage {
post(id: "42", idType: DATABASE_ID) {
title
content
featuredImage {
node {
sourceUrl
altText
}
}
author {
node {
name
email
}
}
}
}This matters more as your frontend complexity grows. If you're building a Next.js app that needs to render dozens of posts with their relationships, WPGraphQL saves bandwidth and reduces round-trip latency. The trade-off is extra complexity—WPGraphQL is another plugin to maintain and debug.
Our recommendation: Start with REST API if your content structure is simple and you want minimal dependencies. Use WPGraphQL if you have complex relationships, deeply nested content, or multiple frontends querying the same data differently.
Building frontends with Next.js or Astro
Your frontend now lives completely outside WordPress. This means you can use modern frameworks and ship static or dynamic sites depending on your needs.
Next.js is a natural choice when you need server-side rendering, API routes, incremental static regeneration, and dynamic interactivity. Here's how you'd fetch posts from a headless WordPress instance:
// pages/blog/[slug].js
export async function getStaticProps({ params }) {
const res = await fetch(`https://your-wp-instance.com/wp-json/wp/v2/posts?slug=${params.slug}`);
const posts = await res.json();
if (!posts.length) {
return { notFound: true };
}
return {
props: {
post: posts[0],
},
revalidate: 3600, // Revalidate every hour
};
}
export async function getStaticPaths() {
const res = await fetch('https://your-wp-instance.com/wp-json/wp/v2/posts?per_page=100');
const posts = await res.json();
return {
paths: posts.map((post) => ({
params: { slug: post.slug },
})),
fallback: 'blocking',
};
}This setup rebuilds your post pages every hour or on-demand when WordPress content changes (via webhooks). Your blog is now a fast, statically generated Next.js app that can scale to millions of readers without hitting your WordPress database at runtime.
Astro takes a different approach—it's optimized for static site generation and sends minimal JavaScript to the browser. If your headless WordPress is primarily delivering static content with minimal client-side interactivity, Astro can be more efficient:
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const response = await fetch('https://your-wp-instance.com/wp-json/wp/v2/posts?per_page=100');
const posts = await response.json();
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
---
<article>
<h1>{post.title.rendered}</h1>
<p>
</p></article>The choice between Next.js and Astro depends on your interactivity needs. Next.js when you need rich client-side features, Astro when content delivery is your primary goal. Both work well with headless WordPress.
Preview and authentication—the hard part
Here's where headless WordPress gets uncomfortable: content preview and editor authentication.
In traditional WordPress, an editor clicks "Preview" and sees their draft post on the live frontend. In headless WordPress, your draft post lives in the WordPress database, but your frontend is a separate Next.js or Astro app. How does an editor preview unpublished content?
The standard solution is a preview URL that includes a secret token:
// In WordPress, generate a preview token
$token = wp_generate_password( 32, false );
add_post_meta( $post_id, '_preview_token', $token );
// Create a preview link
$preview_url = "https://your-frontend.com/preview?token=" . $token . "&postId=" . $post_id;Your Next.js frontend validates that token and temporarily serves unpublished content:
// pages/preview.js
export default function PreviewPage({ post, error }) {
if (error) return <p>Preview unavailable: {error}</p>;
return (
<div>
<h1>{post.title.rendered}</h1>
<p>
</p><p>
This is a draft preview. Not visible to the public.
</p>
</div>
);
}
export async function getServerSideProps({ query }) {
const { token, postId } = query;
// Validate token against WordPress
const response = await fetch('https://your-wp-instance.com/wp-json/wp/v2/posts/' + postId, {
headers: {
'Authorization': 'Bearer ' + token,
},
});
if (!response.ok) {
return { props: { error: 'Invalid token' } };
}
const post = await response.json();
return { props: { post } };
}Authentication is similar. In traditional WordPress, editors log into wp-admin. In headless WordPress, your admin panel might be a custom React app or you might just use wp-admin for editing and rely on a separate authentication layer (JWT tokens, OAuth) for the frontend.
Many teams use a plugin like WPFront User Role Editor or build custom endpoint protection with Basic Auth over HTTPS for initial development, then migrate to JWT tokens for production. The key is that your frontend and WordPress backend must share a secret or token mechanism.
An ideal use case: editorial teams with custom UIs
Where headless WordPress shines is when your editorial team needs something more sophisticated than the default WordPress admin, but you still want WordPress as your CMS backend.
Imagine a travel SaaS where editors manage destinations, hotel partnerships, and curated itineraries. The standard WordPress UI isn't built for this workflow. But WordPress stores the data reliably, has excellent user and permission management, and is familiar to non-technical editors.
You build a custom React dashboard that queries WordPress via REST API or WPGraphQL. Editors never see wp-admin; they work entirely in your custom interface. Your designers aren't constrained by WordPress theme limitations. Your frontend team can ship UI improvements without worrying about breaking WordPress plugins.
| Scenario | Traditional WordPress | Headless WordPress |
|---|---|---|
| Simple blog | Good fit. Use an existing theme. | Overkill. Extra complexity. |
| Multi-channel content (web, app, API) | Requires plugins and workarounds. | Natural fit. Query same content everywhere. |
| Complex editorial workflow | Limited by WordPress UI. | Build a custom dashboard. |
| High-traffic content site | Needs caching plugins. | Static generation or edge caching. |
This is where we've seen headless WordPress work best: e-commerce sites that need product content synced across multiple frontends, SaaS platforms where marketing and product share a content source, and media companies managing video, podcasts, and articles from a single backend.
Common mistakes
- Exposing the WordPress URL publicly. If your frontend queries
https://wp.yourcompany.comdirectly from the browser, you've just advertised that you're running WordPress. Use an API gateway or proxy layer in production. - Forgetting to invalidate cache after publishing. If you're using incremental static regeneration, a webhook from WordPress to your Next.js app must trigger a rebuild. Without it, readers see old content for hours.
- Building custom preview logic instead of using a plugin. WP Preview Anything or similar plugins handle this correctly. Don't roll your own.
- Assuming WPGraphQL is always better than REST. If your data is simple and flat, REST API's simplicity often wins. WPGraphQL adds debugging overhead when queries fail.
- Over-complicating authentication. Many headless WordPress implementations start with JWT tokens but end up needing role-based access control, which pushes you back toward custom middleware or plugins.
Key takeaways
- Headless WordPress separates your CMS backend from frontend rendering, enabling modern UIs and multi-channel content delivery.
- Choose REST API for simple content structures, WPGraphQL when you have complex relationships or multiple frontends with different data needs.
- Next.js works well for dynamic, interactive frontends; Astro for static content-heavy sites. Both integrate naturally with WordPress APIs.
- Preview and authentication require extra work—plan for webhook-based cache invalidation and token-based access control from day one.
- Headless WordPress is most valuable when editorial teams need custom interfaces, content must reach multiple platforms, or frontend performance is critical. For simple blogs, traditional WordPress is usually enough.






Comments
Loading comments…