“Headless WordPress” used to mean a research project. In 2026 it is just one more way to ship a WP site — but it is not always the right one. Here is how I decide, and the exact wiring I use when the answer is yes.
01When headless makes sense
- You need top-of-the-line performance and the client has a complex Gutenberg-style editor experience they do not want to give up.
- You are building a marketing site that integrates with a React app elsewhere and want one frontend stack.
- You have an existing WP install with years of content and do not want to migrate the CMS — just modernize the frontend.
- You want preview, scheduling, and authoring workflows that no headless CMS does as well as WordPress.
02When it absolutely does not
- Tiny brochure sites. Classic WP is faster to ship.
- Clients who insist on a page-builder plugin (Elementor, Divi). Those do not translate to headless cleanly.
- You have no Node deployment story. Adding Vercel/Netlify-flavored devops to a team that only knows cPanel is a tax.
03The stack I use
- WordPress + ACF Pro as the CMS.
- WPGraphQL + WPGraphQL for ACF as the API layer.
- Next.js 15 (App Router) as the frontend.
- TypeScript + GraphQL Codegen for typed queries.
- Vercel for deploy with ISR.
04The WordPress side
Required plugins: wpgraphql, wpgraphql-acf, wp-graphql-jwt-authentication (for previews), and advanced-custom-fields-pro (you already have this).
Expose your ACF fields under “Show in GraphQL” on each field group. That is it on the CMS side — your existing theme can stay (you will keep it for the WP-admin UI even though it does not render the frontend).
05The Next.js side
A typical page fetch:
// app/[slug]/page.tsx
import { getPage } from "~/lib/wp";
export default async function Page({ params }: { params: { slug: string } }) {
const page = await getPage(params.slug);
if (!page) notFound();
return (
<article>
<h1>{page.title}</h1>
<div dangerouslySetInnerHTML={{ __html: page.content }} />
</article>
);
}
export const revalidate = 60; // ISR: re-fetch at most every 60s
Keep the data layer thin: one helper file (lib/wp.ts) with the GraphQL queries and typed return values. Generate types with GraphQL Codegen so you never guess what a field returns.
06Editor previews
The hardest part of headless WP is making previews work. The editor saves a draft and expects to see it on the frontend.
The pattern: a Next.js preview route that takes a JWT, sets a preview cookie, and renders the page with draft data instead of publish data. Wire it up properly and editors do not notice they are on a headless stack.
07Lessons from real projects
- Cache aggressively. Use ISR or on-demand revalidation. Do not hit WordPress on every request.
- Image optimization is your job now. WP’s built-in image sizes do not help. Use Next.js
<Image>or Cloudinary. - Auth is your job. If you need gated content, you are not getting WP’s cookie auth for free.
- Document the deploy. Two stacks means two times the README. Do not skip this.
Headless WP is not magic. It is a deliberate trade — performance and frontend flexibility in exchange for ops complexity. When it fits, it really fits.