Docs / CMS & Website Platforms / Set Up Headless WordPress with WPGraphQL

Set Up Headless WordPress with WPGraphQL

By Admin · Mar 15, 2026 · Updated Apr 24, 2026 · 369 views · 3 min read

Headless WordPress decouples the WordPress admin and content management from the frontend, using WordPress as a pure content API. WPGraphQL adds a performant GraphQL API to WordPress, enabling modern frontends built with React, Next.js, or any framework to consume WordPress content. This guide covers setting up headless WordPress with WPGraphQL on your VPS.

Why Headless WordPress?

  • Best of both worlds: WordPress's powerful editor and plugin ecosystem with a modern frontend
  • Performance: Static or server-rendered frontends are significantly faster than PHP-rendered WordPress
  • Security: The WordPress admin isn't publicly accessible to visitors
  • Flexibility: Build frontends in any framework — React, Vue, Svelte, Astro
  • Multi-channel: Same content API serves websites, mobile apps, and more

Set Up WordPress Backend

# Install WordPress (if not already installed)
cd /var/www
curl -O https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz
mv wordpress wp-backend

# Create database
mysql -u root -p  ({
    slug: post.slug,
  }))
}

export default async function PostPage({
  params,
}: { params: { slug: string } }) {
  const { data } = await client.query({
    query: GET_POST_BY_SLUG,
    variables: { slug: params.slug },
  })

  if (!data.post) notFound()

  return (
    <article className="max-w-3xl mx-auto px-4 py-12">
      <h1 className="text-4xl font-bold">{data.post.title}</h1>
      <div
        className="prose prose-lg mt-8"
        dangerouslySetInnerHTML={{ __html: data.post.content }}
      />
    </article>
  )
}

On-Demand Revalidation

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const secret = req.headers.get('x-revalidate-secret')
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const body = await req.json()

  // Revalidate the specific post and the blog listing
  if (body.post_type === 'post') {
    revalidatePath(`/blog/${body.slug}`)
    revalidatePath('/blog')
  }

  revalidatePath('/')
  return NextResponse.json({ revalidated: true })
}

# In WordPress, install a webhook plugin to POST to this endpoint
# whenever content is published or updated

Security: Lock Down the WordPress Backend

# Nginx config — restrict WordPress admin access
server {
    listen 443 ssl http2;
    server_name wp.yourdomain.com;

    root /var/www/wp-backend;
    index index.php;

    # Allow GraphQL endpoint publicly
    location /graphql {
        try_files $uri /index.php?$args;
    }

    # Restrict wp-admin to specific IPs
    location /wp-admin {
        allow YOUR_IP;
        deny all;
        try_files $uri $uri/ /index.php?$args;
    }

    # Block XML-RPC
    location = /xmlrpc.php { deny all; }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Best Practices

  • Use ISR (Incremental Static Regeneration) for the best balance of performance and content freshness
  • Set up webhooks for instant revalidation when content changes in WordPress
  • Add Yoast SEO + WPGraphQL SEO for comprehensive SEO data in your frontend
  • Use preview mode with WordPress draft previews in your Next.js frontend
  • Restrict the WordPress backend to admin IPs only — the public only accesses your frontend
  • Cache GraphQL responses at the Nginx level for frequently-accessed content

Was this article helpful?