Headless WordPress is transforming how developers build web experiences. By decoupling the frontend and backend, it offers superior flexibility, speed, and control. This guide walks you through creating a headless WordPress site using WPGraphQL and Next.js in 2025, helping you harness the full power of a modern web stack.

What is Headless WordPress?

In a traditional WordPress setup, your themes and plugins render the frontend through PHP. A headless approach decouples this entirely. WordPress acts only as a content management backend, while a separate frontend—like one built with React, Vue, or in our case, Next.js—renders the content.

Benefits:

  • Performance: Static site generation (SSG) improves loading speed and scalability.
  • Security: The public site doesn’t expose WordPress admin endpoints.
  • Scalability: Easily integrate with microservices and APIs.
  • Flexibility: Use modern tools and frameworks for UX.

Why WPGraphQL and Next.js?

WPGraphQL is a free plugin that adds a GraphQL server to WordPress, allowing structured data fetching.

Next.js is a React framework supporting SSR and SSG, essential for SEO and performance.

Together, they:

  • Enable cleaner, faster API queries.
  • Support rendering content statically or dynamically.
  • Allow developers to use modern frontend architectures.

Prerequisites

  • A local WordPress site (via LocalWP or similar).
  • Node.js and npm installed.
  • Basic knowledge of React and GraphQL.

Step 1: Set Up WordPress Backend

  1. Install WordPress locally.
  2. Go to Settings > Permalinks and set to “Post name”.
  3. Install and activate these plugins:
  4. Create some posts, pages, and optionally custom post types.

Step 2: Create Next.js App

  1. Initialize a new app:
npx create-next-app headless-wp
cd headless-wp
  1. Install dependencies:
npm install @apollo/client graphql
  1. Create .env.local and add:
NEXT_PUBLIC_WORDPRESS_API_URL=http://localhost:10003/graphql

Step 3: Set Up Apollo Client

Create a new file: lib/apollo.js

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: process.env.NEXT_PUBLIC_WORDPRESS_API_URL,
  cache: new InMemoryCache()
});

export default client;

Step 4: Query Posts from WordPress

Create GraphQL query: lib/queries.js

import { gql } from '@apollo/client';

export const GET_POSTS = gql`
  query GetPosts {
    posts {
      nodes {
        id
        title
        slug
        excerpt
      }
    }
  }
`;

Create pages/index.js:

import client from '../lib/apollo';
import { GET_POSTS } from '../lib/queries';

export default function Home({ posts }) {
  return (
    <div>
      <h1>Recent Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <a href={`/posts/${post.slug}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  const { data } = await client.query({ query: GET_POSTS });
  return {
    props: { posts: data.posts.nodes },
    revalidate: 10
  };
}

Step 5: Create Dynamic Post Pages

  1. Create pages/posts/[slug].js
import client from '../../lib/apollo';
import { gql } from '@apollo/client';

const GET_POST = gql`
  query GetPost($slug: ID!) {
    post(id: $slug, idType: SLUG) {
      title
      content
    }
  }
`;

export default function Post({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </div>
  );
}

export async function getStaticPaths() {
  const { data } = await client.query({
    query: gql`
      query AllSlugs {
        posts {
          nodes {
            slug
          }
        }
      }
    `
  });

  const paths = data.posts.nodes.map(post => ({ params: { slug: post.slug } }));

  return {
    paths,
    fallback: true
  };
}

export async function getStaticProps({ params }) {
  const { data } = await client.query({
    query: GET_POST,
    variables: { slug: params.slug }
  });

  return {
    props: {
      post: data.post
    },
    revalidate: 10
  };
}

Step 6: Add SEO Tags

Use next/head in each page:

import Head from 'next/head';

<Head>
  <title>{post.title}</title>
  <meta name="description" content={post.excerpt} />
</Head>

Step 7: Deploy to Vercel

  1. Push to GitHub.
  2. Go to Vercel and import the repo.
  3. Set your environment variable: NEXT_PUBLIC_WORDPRESS_API_URL
  4. Click deploy.

Optimization Tips

  • Use Incremental Static Regeneration (ISR).
  • Use CDN caching (Vercel does this automatically).
  • Enable image optimization with next/image.
  • Monitor performance with Google PageSpeed Insights.

Internal Resources

External Resources

Conclusion

Building a headless WordPress site using WPGraphQL and Next.js empowers developers with flexibility, performance, and scalability. As more businesses demand lightning-fast experiences and modern UIs, this architecture becomes increasingly relevant. Follow this 2025 guide to create future-proof, maintainable, and high-performing WordPress sites.