Learn how to use WordPress as a headless CMS with Next.js, using server-side rendering (SSR) for fast, SEO-friendly, and highly scalable front-end applications.
Overview: Why Use Headless WordPress with Next.js?
Headless WordPress separates content management (WordPress) from presentation (Next.js). WordPress exposes content via its REST API or GraphQL, while Next.js renders the front end using React and server-side rendering (SSR).
This approach is ideal when you want:
- Modern React-based front-end experiences
- Better performance and caching control
- Improved SEO through SSR and clean URLs
- Scalability across multiple channels (web, apps, kiosks)
Key Concepts: Headless, SSR, and the WordPress API
Headless WordPress
In a headless setup, WordPress still stores posts, pages, media, and custom fields, but it no longer renders the public-facing theme. Instead, it exposes content via APIs that your Next.js app consumes.
Common API options:
- WordPress REST API – built-in, JSON-based endpoints like
/wp-json/wp/v2/posts - GraphQL (via a plugin) – a single endpoint with flexible queries
Server-Side Rendering (SSR) in Next.js
SSR means pages are rendered on the server at request time, then sent as fully formed HTML to the browser. This is excellent for SEO and dynamic content that changes frequently.
In Next.js, SSR is typically implemented using getServerSideProps in the pages directory or the app directory’s server components and data fetching functions.
Prerequisites
- A working WordPress site (can be staging or local)
- Node.js and npm or yarn installed on your machine
- Basic familiarity with React and JavaScript
- Access to your WordPress admin dashboard
Step 1: Prepare WordPress as a Headless CMS
1.1 Enable the REST API (Default)
Modern WordPress includes the REST API by default. To verify it is available:
- Open a browser and go to
https://your-site.com/wp-json/. - You should see a JSON response with information about your site and available routes.
1.2 Configure Permalinks
Clean permalinks make your content easier to work with and better for SEO.
- Log in to Dashboard ? Settings ? Permalinks.
- Select Post name or a custom structure that includes
%postname%. - Click Save Changes.
1.3 Create Content to Test
Create a few posts and pages so your Next.js app has data to display.
- Go to Dashboard ? Posts ? Add New.
- Add a title, body content, and a featured image.
- Click Publish.
Step 2: Set Up a New Next.js Project
2.1 Create the Project
From your terminal, run:
npx create-next-app@latest headless-wp-next
cd headless-wp-next
2.2 Choose TypeScript (Optional)
During setup, you can choose TypeScript for better type safety, especially useful when working with API responses from WordPress.
2.3 Configure Environment Variables
Create a .env.local file in your project root:
WORDPRESS_API_URL=https://your-site.com/wp-json/wp/v2
Restart your dev server after adding or changing environment variables.
Step 3: Fetch WordPress Content with SSR
3.1 Basic SSR Page Using getServerSideProps
In the pages directory, create pages/index.js (or .tsx):
export async function getServerSideProps() {
const res = await fetch(`${process.env.WORDPRESS_API_URL}/posts?per_page=5`);
const posts = await res.json();
return {
props: {
posts,
},
};
}
export default function Home({ posts }) {
return (
<main>
<h1>Latest Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/posts/${post.slug}`}>
{post.title.rendered}
</a>
</li>
))}
</ul>
</main>
);
}
3.2 What You Should See
When you run npm run dev and visit http://localhost:3000, you should see:
- A heading like “Latest Posts”
- A list of post titles pulled from your WordPress site
- Each title linking to a URL like
/posts/your-post-slug
Step 4: Create Dynamic Post Pages with SSR
4.1 Dynamic Route for Posts
Create a file at pages/posts/[slug].js:
export async function getServerSideProps({ params }) {
const { slug } = params;
const res = await fetch(
`${process.env.WORDPRESS_API_URL}/posts?slug=${slug}`
);
const data = await res.json();
if (!data.length) {
return {
notFound: true,
};
}
return {
props: {
post: data[0],
},
};
}
export default function Post({ post }) {
return (
<article>
<h1>{post.title.rendered}</h1>
<div
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
/>
</article>
);
}
4.2 What You Should See
Clicking a post title on the homepage should now:
- Navigate to a URL like
/posts/example-post - Render the full post title and content from WordPress
- Show HTML formatting (headings, lists, images) as in the WordPress editor
Step 5: Handling SEO with SSR
5.1 Dynamic Meta Tags
Use the Next.js Head component to set meta tags based on WordPress data:
import Head from 'next/head';
export default function Post({ post }) {
const title = post.yoast_head_json?.title || post.title.rendered;
const description = post.yoast_head_json?.description || '';
return (
<>
<Head>
<title>{title}</title>
{description && (
<meta name="description" content={description} />
)}
</Head>
<article>
<h1>{post.title.rendered}</h1>
<div
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
/>
</article>
</>
);
}
5.2 Canonical URLs and Open Graph
If you use an SEO plugin in WordPress, many meta fields are already available in the REST API. You can map them into <meta> tags in Head for canonical URLs, Open Graph, and Twitter cards.
Step 6: Authentication and Protected Content
For public content, anonymous REST API access is enough. For protected content (e.g., members-only posts), you will need authentication.
- Cookie-based auth – for same-domain setups
- JWT or application passwords – for decoupled front-ends
In SSR, you can read cookies or headers in getServerSideProps, validate the user, and then call the WordPress API with appropriate credentials.
Step 7: Performance, Caching, and Deployment
7.1 Caching Strategies
Because SSR runs on every request, caching is critical:
- Use HTTP caching headers from your Next.js responses
- Leverage a CDN or hosting platform edge cache
- Consider hybrid rendering (SSR for some routes, static generation for others)
7.2 Deploying Your Next.js App
When deploying, make sure:
WORDPRESS_API_URLis set in your production environment- Your WordPress site is publicly reachable from the Next.js server
- HTTPS is enabled on both WordPress and your front-end domain
How This Fits with Your Existing WordPress Workflow
Your content editors can continue using WordPress as usual:
- Create and edit posts via Dashboard ? Posts
- Manage media via Dashboard ? Media
- Use custom fields or custom post types as needed
The difference is that instead of a traditional theme, your Next.js application consumes the content and renders it with React, giving you more control over performance, design systems, and multi-channel publishing.
Troubleshooting: Common Issues
- 404 on /wp-json/ – Check that permalinks are set correctly and no security plugin is blocking the REST API.
- CORS errors – Configure CORS headers on your WordPress host to allow requests from your Next.js domain.
- Slow responses – Add caching or a performance plugin on WordPress, and consider limiting
per_pagein API calls.
Next Steps
- Introduce a design system or component library in your Next.js app
- Use incremental static regeneration (ISR) for less frequently updated content
- Explore GraphQL for more efficient and flexible queries
Video