Next.js 13 and WPGraphQL in headless WordPress

Francis Agulto Avatar


In this article, you will learn how to create a simple headless WordPress app using Next.js 13 and WPGraphQL. This tutorial assumes a basic understanding of React , Next.js 12, and WordPress. Using the prepared GitHub repository will allow us to focus on points specific to Next.js 13 and some of its new features.  By the end of this tutorial, you will be able to understand:

  • The App router 
  • Roles of Folders and Files in the new App Router
  • File Conventions in the new App Router
  • Default React Server Components in Next.js 13
  • Adding WPGraphQL Queries / Data Fetching

Getting Started

To get started, you’ll need to make sure that you have both Node.js and npm installed.  In order to make this work, you need to be on Node.js 16.14 or later. If you aren’t sure whether or not you have this software installed, you can run the following commands in your terminal:

​​node -v

npm -v

The terminal output should either tell you which versions you have installed or that it cannot find the commands node or npm to run them. Using a tool like nvm (Node Version Manager) to install and manage versions of Node.js on your machine can be helpful when working on multiple projects.

Configure Your WordPress Site

  1. Set up a WordPress site on Local, WP Engine, or any host of your choice
  2. Install and activate the WPGraphQL plugin.  Activate debug mode for it:

To activate Debug Mode open the GraphQL > Settings menu and check the option labeled Enable GraphQL Debug Mode and then click Save Changes.

3.Check Permalink Settings:

All of the code in this article assumes that you have the Post name option selected for your permalink settings. To check or modify this setting, open the Settings > Permalinks menu in the WP Admin dashboard.

Create 5 Posts

Make sure to create content for 5 posts on your WordPress site.  We will be querying for the first 5 posts from WPGraphQL.  I am a bit of a Star Wars nerd so I created some Star Wars content.

Next.js 13 Setup

We now have our WordPress backend configured.  Let’s get stoked on setting up our Next.js 13 front-end.

The rest of this tutorial is based on the files that can be found in my GitHub repository. This repository is based on the default Next.js template created with create-next-app@latest CLI tool, with some additional scaffolding and styles I added on my own using the Tailwind CSS utility framework.

  1. Clone down the repo locally, CD into that directory, and run npm run install.
  2. In the root of the project, create a .env.local file and add the following key and the value:   


(add your WordPress backend’s WPGraphQL URL)

This makes the WPGraphQL URL of our WordPress site available to our application through an environment variable. By default, Next.js will look for a file named .env.local in the root directory of the project to load any environment variables.

Run npm run dev and you should be able to visit http://localhost:3000 and see this page with the first 5 posts from your WordPress backend:

Next.js 13 Paradigm Changes

Before we dive into the repository and code for this example walkthrough, let’s quickly define the new paradigm structure in Next.js 13.

App Router

In the root of the project, Next.js 13 uses what is called the App Router.  In previous versions of Next.js, they used what is called the Pages directory.  
The app router is represented by a top-level folder in the project named app.

Within the App folder, you nest other folders to define routes, and the files inside these folders are used to define the UI.  By default, all the files created in the app router are React Server Components. 

The Pages Router in Next.js 12 utilized a file-based router built on the concept of pages. When a file is added to the pages directory it’s automatically available as a route.

The App Router in comparison handles the overall routing and navigation for your entire application. It is responsible for rendering the correct pages based on the URL and managing the transitions between pages.  A folder defines a route in this system.  A route is a single path of nested folders, from a root folder down to a leaf folder which will include files that are used to create the UI.

Route Segments

Routes are commonly called route segments since they are folders.  Dynamic routes are referred to as Dynamic segments.

When you create a route, you add it to the app directory.  When you want that route to be dynamic, you add brackets around the name of that folder which is very similar to Next.js 12.  The difference is that Next.js 12 uses files for this, whereas Next.js 13 uses folders.

For example:


If you want a folder to be bypassed as a route, you wrap parentheses around it and that tells Next.js to not add it as a route in the URL.  An example would be if you wanted a folder to be named dashboard but not part of the URL: (dashboard)

For our example, we will be using the folder named post which will be our single post route, and the parameter we will be grabbing the single post data by will be the URI. 

Folders And Files

The folders in Next.js 13 represent the route and path of what you will see in the URL.

Files on the other hand are used to create the UI and are rendered on the browser in relation to the route segment they are nested in.  The file conventions are provided by Next.js 13 to create a UI with specific behavior in nested routes.  In our project example, we will be using page, loading, and layout with the extension .jsx.

Data Fetching

Next.js extends the fetch Web API to allow you to configure the caching and revalidating behavior for each fetch request on the server. React extends fetch to automatically memoize fetch requests while rendering a React component tree.

You can use fetch with async/await in Server Components, in Route Handlers, and in Server Actions.  In our example, we will use the fetch API to grab data from our WPGraphQL endpoint.

Rendering Server and Client Components

Next.js 13, allows you the capability to create hybrid applications so that parts of the code can be rendered on the server or the client.  

By default, components in Next.js 13 are React Server Components that are rendered on the server and can be optionally cached on it.  This is now the default render method and there are many benefits to doing the rendering on the server.  

Our example will take advantage of the benefits it gives for data fetching and caching. 

Example Project

Now that we have a high-level understanding of the new paradigm in Next.js 13, let’s dive into our repository to see it in action.

Home Page

Starting in the root of the App router folder, we have a page.jsx file.  This file represents the home page of the site:

import Link from "next/link";
import { Suspense } from "react";
import Loading from "./loading";

async function getPosts() {
  const query = `
    posts(first: 5) {
      nodes {

  const res = await fetch(
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      next: {
        revalidate: 0,

  const { data } = await res.json();

  return data.posts.nodes;

export default async function PostList() {
  const posts = await getPosts();

  return (
      { => (
        <div key={post.uri} className="card">
          <Suspense fallback={<Loading />}>
            <Link href={`/post/${post.uri}`}>
                  __html: post.content.slice(0, 200) + "...",

Code language: JavaScript (javascript)

At the top of the file, we are going to use client-side navigation with the next/link component, and the Suspense component from React to handle asynchronous data loading and a custom Loading state.

In order to fetch our data, this component by default is a React Server Component and we define an async function named getPosts.  This is a query that fetches the first 5 posts, with the fields we declare.

Then, we await a GET request using fetch to grab data from the WPGraphQL endpoint.  This request will be cached on the network and not the Next.js server.  In the object, we have the content type header of the request and the next.revalidate property with the value set to 0.  This tells Next.js to bypass the cache and have everything fetched anew upon every request.  

After that, we deserialize the response JSON and extract the data field, returning only the nodes from posts

Then the main functional component is PostList and the variable type calls getPosts to fetch the posts and store them in the posts variable.

Lastly, we return the JSX and the Link needed to allow the user to click on the post title and excerpt to go to its details page and wrap the component needed.  This gets us the data rendered on the browser which we saw earlier upon setup.

Layout & Loading File

Staying in the root of the app folder, we see the new naming file conventions. These conventions are special files that tell Next.js what behavior to execute in the UI.

In the old Pages Router, you could name a file whatever you wanted it to be and still use it as an actual layout that functioned as a wrapper for your application.    

We have a layout component that wraps any page component in the application and this automatically happens when you create a layout.jsx file.  This is a Layout file and it is named Layout.  We also have a Navbar file that is being imported from the components folder in the layout file.  

The other file to notice is named loading.jsx which is our custom loading component.  This is the file convention naming as well that Next.js 13 uses. 

Making the Post Page Dynamic

In order to give users the ability to click on a post from our home page and navigate them to a single post detail page, we need to add a folder that represents the route and URL of that dynamic path first.  In our project, we add it to the root of the app directory and call it post

The post folder will represent the route segment in the URL and then we need to add a dynamic route segment to this in order to grab a single post and its details via whatever parameter we specify.  In this project, we will grab it by its URI.

In the Post folder, we create another nested folder called uri and wrap brackets on it.  This tells Next.js to make this dynamic which allows this segment to be passed as the params prop to the page.

Now that we have our route segments set up and the parameter dynamically set, we need to create the UI with a file for this route.  We do that by adding a page.jsx file in the nested [uri] folder within the Post folder.  Let’s break down what is going on here.  

import { Suspense } from "react";
import Loading from "../../loading";

async function getPost(uri) {
  const query = `
  query GetPostByUri($uri: ID!) {
    post(id: $uri, idType: URI) {

  const variables = {

  const res = await fetch(process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    next: {
      revalidate: 60,
    body: JSON.stringify({ query, variables }),

  const responseBody = await res.json();

  if (responseBody && && {
  } else {
    throw new Error("Failed to fetch the post");

export default async function PostDetails({ params }) {
  const post = await getPost(params.uri);

  return (
      <Suspense fallback={<Loading />}>
        <div className="card" key={post.uri}>
          <p dangerouslySetInnerHTML={{ __html: post.content }} />

Code language: JavaScript (javascript)

It is very similar to the code on our home page except, the WPGraphQL query is grabbing a single post via its URI.

The 2 major differences are the WPGraphQL query which is an async function that takes an id parameter and the query string fetches a single post by its uri.

Then we set up a variables object containing the uri.

In the execution of the fetch request, we use the POST method, and the next.revalidate method is set to 60 which tells Next.js to use time-based revalidation every 60 seconds.  I wanted to show an example of the optionality you can have in Next.js 13 at the function level on caching, time based revalidation, and method requests.  

Lastly, the main functional component renders the JSX onto the browser when we visit a post link detail:


Next.js 13 is a new version of the most used meta framework on top of React. It introduces new ways to handle data, create routes and files as well as rendering methods.  Mix it with headless WordPress and WPGraphQL and you have a super stoked combination!  We hope you have a better understanding of how it all works together.

Stay tuned for more Next.js 13 and headless WordPress content coming soon!!

As always, stoked to hear your feedback and any questions you might have on headless WordPress! Hit us up in our Discord!