Headless WordPress separates content management from page delivery. Editors keep the familiar WordPress dashboard, while a Next.js application fetches the content and renders the public website. This guide explains how the pieces fit together, how to build the basic connection, and when the added complexity is worth accepting.
The short answer is simple: use headless WordPress when your frontend needs capabilities a normal WordPress theme cannot provide cleanly. Do not choose it only because Next.js sounds faster or more modern.
What changes in a headless setup
A traditional WordPress site handles two jobs in one application. WordPress stores and manages content, then a PHP theme turns that content into pages for visitors. Plugins, menus, previews, search, forms, and redirects all live in the same environment.
A headless setup splits those responsibilities:
- WordPress becomes the content management system. Editors create posts, pages, media, categories, and custom fields.
- The WordPress REST API or WPGraphQL exposes the content. The frontend requests structured data instead of receiving a finished HTML page.
- Next.js becomes the public website. It turns the content into routes, layouts, metadata, and interactive components.
- A hosting platform serves the frontend. Vercel is common, but any platform that supports Next.js can work.
The WordPress REST API Handbook describes the API as the structured interface for posts, pages, taxonomies, users, and other WordPress data. Public content is generally available without authentication. Private content and write operations remain protected.
Headless WordPress does not remove WordPress. It moves WordPress behind a separate presentation layer.
That distinction matters. You now operate two applications, two deployments, and a contract between them. The result can be excellent, but it is not automatically simpler.
Decide whether headless is the right architecture
Headless is a strong fit when the public experience is genuinely custom. Examples include a content platform shared by a website and mobile app, a multilingual publication with complex routing, an interactive product experience, or a company that already has a React and Next.js development team.
It can also make sense when several frontends need the same content. A church network might publish a sermon once in WordPress, then distribute it to a public website, campus microsites, a digital guide, and a mobile app. WordPress remains the editorial source of truth while each frontend presents the content for its audience.
A conventional WordPress build is usually better for a brochure site, local service business, small WooCommerce store, or marketing site managed mostly through page-builder plugins. Those projects benefit from the integrated preview, plugin, form, search, and theme ecosystem.
| Requirement | Traditional WordPress | Headless WordPress + Next.js |
|---|---|---|
| Editors need page-builder control | Best fit | Usually frustrating |
| One simple marketing site | Best fit | Usually unnecessary |
| Shared content across apps | Possible, but awkward | Strong fit |
| Highly custom React interface | Requires theme integration | Strong fit |
| Plugin renders frontend output | Works directly | Often needs rebuilding |
| Small maintenance budget | Lower operational burden | Higher operational burden |
| Independent frontend deployments | Limited | Built into the architecture |
If the project cannot name a concrete requirement in the right column, stay traditional. A well-configured WordPress theme with caching can be fast, secure, and much easier to own.

Headless architecture turns content delivery into an explicit workflow between WordPress and the frontend.
Build the minimum working connection
Start with the smallest useful version: one WordPress installation, one Next.js application, and the built-in REST API. Add GraphQL, custom authentication, and advanced preview behavior only when the project proves it needs them.
1. Prepare WordPress as the CMS
Use a normal WordPress installation with HTTPS and readable permalinks. Create a post, give it a featured image, and confirm that the post appears at this endpoint:
https://cms.example.com/wp-json/wp/v2/posts
The official REST API endpoint reference lists the standard routes. Posts live at /wp/v2/posts, pages at /wp/v2/pages, media at /wp/v2/media, and categories at /wp/v2/categories.
For a first build, request only the fields you need. WordPress supports the _fields parameter, which keeps responses smaller:
https://cms.example.com/wp-json/wp/v2/posts?_fields=id,slug,date,title,excerpt,content,featured_media
If you need featured-image details in the same response, add _embed=wp:featuredmedia. WordPress will include related media data under _embedded, which avoids a separate request for every card in a blog grid.
2. Fetch the content in Next.js
In the Next.js App Router, keep the WordPress request in a server-side data function. Public API requests do not need credentials.
type WpPost = {
id: number
slug: string
date: string
title: { rendered: string }
excerpt: { rendered: string }
content: { rendered: string }
}
export async function getPosts(): Promise<WpPost[]> {
const response = await fetch(
`${process.env.WORDPRESS_URL}/wp-json/wp/v2/posts?_fields=id,slug,date,title,excerpt,content`,
{ next: { revalidate: 300, tags: ["wordpress-posts"] } }
)
if (!response.ok) {
throw new Error(`WordPress returned ${response.status}`)
}
return response.json()
}
Store WORDPRESS_URL as a server environment variable. Even though the public endpoint is not secret, centralizing it makes staging and production easier to separate.
Next.js documents time-based revalidation with the next.revalidate fetch option in its caching guide. The example above allows a cached response to refresh every five minutes. That is a reasonable starting point for a business blog, but it is not the best final publishing workflow.
3. Generate routes by WordPress slug
Create a route such as app/blog/[slug]/page.tsx, then fetch the matching WordPress post with the slug query parameter:
export async function getPost(slug: string): Promise<WpPost | null> {
const response = await fetch(
`${process.env.WORDPRESS_URL}/wp-json/wp/v2/posts?slug=${encodeURIComponent(slug)}`,
{ next: { revalidate: 300, tags: ["wordpress-posts"] } }
)
if (!response.ok) throw new Error("WordPress request failed")
const posts: WpPost[] = await response.json()
return posts[0] ?? null
}
Use notFound() when the post does not exist. Build page metadata from the returned title and excerpt, and make sure canonical URLs point to the public Next.js domain rather than the hidden CMS domain.
WordPress returns rendered HTML in fields such as content.rendered. Treat that HTML as untrusted input at the frontend boundary. WordPress sanitizes content according to user permissions, but your renderer should still have a deliberate policy for scripts, iframes, embeds, and custom block markup.
4. Revalidate when editors publish
Waiting five minutes is acceptable during development. Editors expect publishing to feel immediate in production.
Create a protected Next.js route handler that receives a webhook from WordPress and calls revalidateTag("wordpress-posts") or revalidates the affected path. Protect the endpoint with a long random secret, compare the provided value, reject unknown requests, and log failures without logging the secret itself.
The Next.js ISR documentation explains both timed and on-demand revalidation. It also notes an important safety behavior: if regeneration fails, Next.js continues serving the last successfully generated page rather than replacing it with the failed result.
On the WordPress side, a small plugin can send the webhook when save_post runs for a published post. Debounce repeated saves and ignore autosaves and revisions. Include the post slug or ID so the frontend can invalidate only the routes that changed.
Handle previews, media, and custom fields deliberately
The basic content connection is the easy part. Editorial details determine whether the system feels finished.
Draft previews
A normal WordPress preview URL points to the PHP frontend, not your Next.js application. For a useful editorial preview, WordPress must send the editor to a protected Next.js preview route. That route validates a secret and slug, enables Draft Mode, fetches authenticated draft content, and redirects to the public route shape.
The official Next.js Draft Mode guide shows this pattern and warns against redirecting directly to an unchecked URL parameter. Verify that the requested slug exists, then redirect using the path returned by the CMS.
Private drafts require authentication. WordPress Application Passwords are designed for API access, are stored hashed, are shown only once, and can be revoked independently. The WordPress Application Passwords guide recommends using them over HTTPS. Create a dedicated low-privilege preview user rather than using an administrator account.
Images
WordPress media URLs usually point to the CMS host. Add that hostname to the Next.js image configuration, or proxy and transform the images through your delivery platform. Preserve width, height, alt text, caption, and source URL in your data mapping.
Do not assume the featured media object is present. Posts can have no featured image, media can be deleted, and imported content often has incomplete metadata. Build a fallback image and test the empty state.
Custom fields and block content
The REST API handles core fields well. Custom fields appear only when registered with show_in_rest, and plugin data varies by implementation. If the project depends on many custom post types and nested fields, WPGraphQL can provide a more precise query layer.
WPGraphQL is a plugin, not part of WordPress core. Its quick-start guide explains that activating the plugin adds a GraphQL endpoint and an in-dashboard GraphiQL explorer. Use it when the frontend benefits from selecting a specific content graph. Do not add it merely to replace one simple REST request.
Gutenberg blocks add another decision. You can render content.rendered as HTML, map specific block types to React components, or use a hybrid approach. Rendering HTML ships faster and preserves plugin output. Mapping blocks gives the frontend more control but turns every supported block into code your team must maintain.

Separating the CMS and frontend adds a clear security boundary, plus another system to monitor and maintain.
Secure and operate both sides
A headless architecture changes the attack surface. Hiding the default theme does not make WordPress secure. The CMS still accepts logins, runs PHP, exposes API routes, processes uploads, and depends on plugins.
Use these controls from the start:
- Keep WordPress core, plugins, themes, PHP, and the database maintained.
- Put the CMS on a dedicated subdomain such as
cms.example.comwith HTTPS. - Restrict administrative access with strong accounts, multifactor authentication, rate limiting, and a web application firewall.
- Use a dedicated integration user with the minimum capability required.
- Keep Application Passwords and webhook secrets in server-side environment variables.
- Allow only the API routes and HTTP methods your frontend needs.
- Monitor WordPress API errors, frontend fetch failures, webhook delivery, build status, and stale content.
- Back up both WordPress data and deployment configuration.
Do not expose an Application Password in client-side JavaScript. Browser bundles are public. Authenticated WordPress requests should originate from a Next.js server component, route handler, or other trusted backend.
Also decide what happens when WordPress is unavailable. Cached public pages should continue working. New builds and uncached requests should fail visibly in monitoring, not quietly render empty pages. Error handling is part of the architecture, not cleanup for later.
Plan for the features WordPress used to provide
The frontend split means some familiar features no longer come for free. Before approving the architecture, assign an owner and implementation for each item:
| Feature | Headless replacement |
|---|---|
| Menus | Fetch menu data through an API or model navigation separately |
| Search | Build an API search route or use a search service |
| Forms | Use a frontend form service or custom server handler |
| SEO metadata | Map fields into Next.js metadata and structured data |
| Redirects | Sync redirect rules to the frontend platform |
| Preview | Protected Draft Mode flow |
| Comments | Custom frontend plus authenticated write endpoint |
| Plugin widgets | Rebuild as frontend components |
| Sitemap | Generate from WordPress content and frontend routes |
This is where many headless projects exceed their original budget. The homepage demo works quickly. Replacing the integrated behavior around it takes longer.
The cost of headless WordPress is not fetching posts. It is rebuilding the connections a normal WordPress site already has.
A practical launch checklist
Before launch, verify the complete publishing path rather than only the homepage:
- Publish, update, unpublish, and delete a test post.
- Confirm the frontend refreshes after each action.
- Preview a draft as an editor who is not an administrator.
- Test posts with no image, long titles, embeds, galleries, and unsupported blocks.
- Confirm canonical URLs, Open Graph images, schema markup, and sitemap entries use the public domain.
- Test WordPress downtime and webhook failure.
- Rotate the preview credential and webhook secret once to prove the process is documented.
- Check redirects when a slug changes.
- Verify the CMS domain is not accidentally indexed as a competing public website.
- Give editors a short guide that explains previews, publishing delay, and which blocks are supported.
Launch only when content operations work without a developer standing beside the editor.
The final decision
Choose headless WordPress with Next.js when the separate frontend creates measurable value: a custom application experience, shared content across channels, independent deployment, or a React interface your team already knows how to maintain.
Stay with traditional WordPress when the project primarily needs pages, forms, local SEO, ecommerce, and easy visual editing. That is not a less advanced choice. It is often the more responsible architecture.
If headless is justified, start with the WordPress REST API, a server-side Next.js fetch layer, protected on-demand revalidation, and a real preview workflow. Keep the first version boring. Add GraphQL, block mapping, and extra services only when a concrete requirement pays for the complexity.