DEV Community

Cover image for Using WordPress as a headless CMS with Next.js
Rob Kendal {{β˜•}}
Rob Kendal {{β˜•}}

Posted on • Originally published at robkendal.co.uk on

Using WordPress as a headless CMS with Next.js

Blog article on connecting WordPress as a headless CMS to Next.js

In part one of using WordPress as a headless CMS with Next.js, we looked at the basics of setting up a WordPress instance so that we can access Posts and Pages and custom content via GraphQL using the Next.js framework. We also created a new Next.js app using the create-next-app tool.

For part two in the series, we're going to take those starting bases and connect the dots to supply content data from WordPress via the WPGraphQL plugin and access it in our Next.js project.

If you like this article, you'll love the other helpful content I post on Twitter. Find me on Twitter @kendalmintcode and say hi.

Cleaning up the new Next.js project

Note: in part one, we created a new Next.js application using the create-next-app tool. I'd recommend going through the steps in part one first, to get that set up before continuing along here.

Out of the box, the create-next-app provided by Next.js adds in a lot of helpful stuff as a starter for ten. However, we can remove some of the cruft to get us down to a basic build and limit any possible confusion.

Files to delete

Open up the project from part one in VS Code (or your favourite IDE) and delete the following files and folders:

  • /pages/api
  • /pages/api/hello.js

Files to edit

Next, we need to amend the /pages/index.js file. This is the main entry point, our home page, for our app. At the moment, it's crammed full of Next.js guides and links and other helpful, but unwanted markup, so let's clear it out.

Open up /pages/index.js and locate the <main> element in the component. Replace everything between the open <main> and closing </main> with the following:

<h1 className={styles.title}>Welcome to our demo blog!</h1>

<p>
  You can find more articles on the{' '}
  <Link href='/blog'>
  <a>blog articles page</a>
  </Link>
</p>
Enter fullscreen mode Exit fullscreen mode

If you've used React Router, you might be familiar with the rather unique-looking way that we're linking to the /blog page. Next.js uses a similar internal routing component as React Router to link to internal pages, it looks like this:

<Link href='/blog'>
  <a>blog articles page</a>
</Link>
Enter fullscreen mode Exit fullscreen mode

You can read more about the Next.js Link element here, but the essence is that you need to declare the <Link> component and add a href="/link-to-your-page" attribute with the path to where you want to link to. Finally, you need to add a single <a> anchor element with whatever name you want to use for the link.

Note: you should add any class names or other typical anchor attributes you wish to the <a> tag not the <Link> component.

One last thing to do here and that's import the Link component. Add the following to the top of the /pages/index.js file:

import Link from 'next/link';
Enter fullscreen mode Exit fullscreen mode

With that done, the entire /pages/index.js file should look like this:

import Head from 'next/head';
import Link from 'next/link';
import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <link rel='icon' href='/favicon.ico' />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Welcome to our demo blog!</h1>

        <p>
          You can find more articles on the{' '}
          <Link href='/blog'>
            <a>blog articles page</a>
          </Link>
        </p>
      </main>

      <footer className={styles.footer}>
        <a
          href='https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app'
          target='_blank'
          rel='noopener noreferrer'
        >
          Powered by{' '}
          <img src='/vercel.svg' alt='Vercel Logo' className={styles.logo} />
        </a>
      </footer>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Files to add

Of course, we need a couple more files that we'll build out over the course of the article. These will handle our blog post routing and data handling, interacting with our WordPress backend.

Add the following folders and files within them:

  • Folder /lib - put this in the root of the project. This will hold any utility files and specifically our API file that will talk to WordPress.
  • File /lib/api.js - this will handle our GraphQL queries and data fetching.
  • Folder /pages/blog - nothing fancy here, just a folder to hold our blog pages.
  • File /pages/blog/index.js - when people visit a route like https://somedomain.co.uk/blog/ this is the page that will serve that request.
  • File /pages/blog/[slug].js - similar to the above, this rather weird looking page will handle individual blog pages, e.g. a domain like https://yourdomain.com/blog/an-interesting-article/.
  • File /styles/Blog.module.css - this is a standard CSS file that will hold styles for our blog list items.
  • File /.env.local - an environment variable file to hold
  • File /styles/Blog.module.css - a modular

That odd looking file name, [slug].js looks really unfamiliar, but it's how Next.js determines dynamic routes within a folder.

We'll cover that next.

Dynamic routing in Next.js

Before we start building out our new pages, it'll be helpful to quickly highlight how dynamic routing in Next.js works.

Out of the box, without doing anything fancy, Next.js will try to match any route you throw at it to a .js file that it finds under the /pages folder in your project.

For example:

  • / will match /pages/index.js
  • /blog/ will match /pages/blog.js or /pages/blog/index.js
  • /contact/thanks will match /pages/contact/thanks.js

However, when it comes to dynamic routes, such as a blog post or product page, we might have one physical page file that acts as a template of sorts, handling an unknown amount of routes.

For this, Next.js will match a filename in the format [param]. So, in our case above where we have the file path /pages/blog/[slug].js, Next.js will call the [slug].js page for the following routes:

  • /blog/my-awesome-blog-post
  • /blog/another-great-post-title
  • /blog/some-final-title-here
  • ...and so on.

You can call this dynamically routed file whatever you like between the [ and ] characters, but you'll be referencing this name inside the file (as you'll soon see), so it makes sense to call it something meaningful. In our case 'slug' is the terms that WordPress uses, so we'll leave it as that.

It's worth looking at the official Next.js documentation on dynamic routing to familiarise yourself with the syntax and conventions to apply them to your app/site.

Fetching data with the api.js file

Now for the real meat and potatoes of the article: fetching data!

There's no right way to build out your files in a project like this, but I tend to prefer building things in a least-dependent to most-dependent order. In our case, the data-fetching isn't dependent on anything else, but the UI-layer depends on this, so it makes sense to start here.

Dealing with environment variables

Some things, like global variables that might change between environments are best stored in (funnily enough) environment variable files, usually created as .env files in the root of your project.

Since we've already created one such file, let's populate it with our WordPress GraphQL URL. Open up the file /.env.local and add the following line:

WP_API_URL=http://demo.robkendal.co.uk/graphql/
Enter fullscreen mode Exit fullscreen mode

Note: you can use my URL as above, but note that a) it might be taken down without notice, and b) you may not get the results you want. It's always best to get your own WordPress instance. Either way, you'll want to add in the url in the format http://your-domain-here.com/graphql/

Next.js comes with built in support for environment variable files. You just have to add a .env.local file in the root of your file and add in what you need. As always, the Next team have great docs on environment variables for you to peruse.

Adding the general fetching function

Open up the /lib/api.js file and let's start adding in our data-fetching magic. The first thing is to add the general fetch function that will handle the talking to our WordPress GraphQL endpoint.

At the top of the file, we'll reference our API URL we just added into the .env file, followed by the fetchAPI function.

const API_URL = process.env.WP_API_URL;

async function fetchAPI(query, { variables } = {}) {
  // Set up some headers to tell the fetch call
  // that this is an application/json type
  const headers = { 'Content-Type': 'application/json' };

  // build out the fetch() call using the API_URL
  // environment variable pulled in at the start
  // Note the merging of the query and variables
  const res = await fetch(API_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify({ query, variables })
  });

  // error handling work
  const json = await res.json();
  if (json.errors) {
    console.log(json.errors);
    console.log('error details', query, variables);
    throw new Error('Failed to fetch API');
  }
  return json.data;
}
Enter fullscreen mode Exit fullscreen mode

This is an asynchronous function as we need to wait for the fetch() call to complete. The rest of the comments should be enough to walk you through the file.

Believe it or not, this is the most complex function in our API file. Whilst not the longest, it does have more moving parts. The upcoming functions we'll be defining next largely outline GraphQL queries that the fetchAPI() function here will handle.

Add function to get blog post listings

From here on out, we'll define our GraphQL queries that will shape the data we want back from WordPress.

Quick tip: the best approach to defining GraphQL queries is to fire up the GraphiQL plugin on the WordPress instance, write your query, test the results and then copy it into your API file. That way, you know it works and hopefully eliminate errors along the way.

As far as queries go, this is quite straightforward. We're looking at all posts, grabbing the first 20 results (for brevity), and ordering them by descending date order.

With these exception of the extraPostInfo ACF custom fields we defined in part one of this series, the rest of the data is standard WordPress data, such as title, id and the slug of the post.

// Notice the 'export' keyword here. We'll be calling this function
// directly in our blog/index.js page, so it needs to be exported
export async function getAllPosts(preview) {
  const data = await fetchAPI(
    `
    query AllPosts {
      posts(first: 20, where: { orderby: { field: DATE, order: DESC}}) {
        edges {
          node {
            id
            date
            title
            slug
            extraPostInfo {
              authorExcerpt
              thumbImage {
                mediaItemUrl
              }
            }
          }
        }
      }
    }
    `
  );

  return data?.posts;
}
Enter fullscreen mode Exit fullscreen mode

Once the query returns, we use the optional chaining operator to return the posts array or undefined if that is unavailable.

You can see that this is a really simple function. There's only two real operations here: 1 to call the fetchAPI() function we defined previously; and 2 to return the data. The largest part of this function is the GraphQL query that Next.js will be passing to WordPress to retrieve our Posts data.

GraphQL is one of those technologies that's quite simple to get to grips with on the surface, but has a great deal of depth if you want to do more complex queries with multiple parts and fragments. I'd recommend having a look around the official GraphQL docs and playing with some of the queries in your WordPress GraphQL playground.

Here's how the same query looked in GraphiQL when I built it, and the results that it returned:

All WordPress posts GraphQL query

Add function to get all blog post slugs

Having fetched a list of blog posts from WordPress with some specific data, now we want to get a list of all possible Posts, but only the slug of each Post.

This function, getAllPostsWithSlug() will be used on our individual blog article page, currently located at /blog/[slug].js.

I'll go into this in more detail when we get to the frontend component, but for now, it's enough to understand that we need to get a list of matching slug values for Next.js to match an individual one (i.e. the one you're visiting) against. That's where this function comes in.

Still in the /lib/api.js file, define a new exported async function, getAllPostsWithSlug() and populate it as follows:

export async function getAllPostsWithSlug() {
  const data = await fetchAPI(
    `
    {
      posts(first: 10000) {
        edges {
          node {
            slug
          }
        }
      }
    }
  `);
  return data?.posts;
}
Enter fullscreen mode Exit fullscreen mode

These sorts of queries will start to look more common and familiar the more that you build them out. You'll start to notice a pattern too where we define a content type (e.g. posts), add an optional filter (e.g. (first: 10000)), then look for edges and a node within that (e.g. the individual content type item) and properties of that content type (e.g. slug).

Add function to get an individual blog post's data

This next GraphQL query is going to be used to pull in data from an individual Post item. It'll be called when viewing a single blog article on the [slug].js page.

Under the last query, define a new exported async function called getPost(). It should look like this:

export async function getPost(slug) {
  const data = await fetchAPI(
    `
    fragment PostFields on Post {
      title
      excerpt
      slug
      date
      featuredImage {
        node {
          sourceUrl
        }
      }
    }
    query PostBySlug($id: ID!, $idType: PostIdType!) {
      post(id: $id, idType: $idType) {
        ...PostFields
        content
      }
    }
  `,
    {
      variables: {
        id: slug,
        idType: 'SLUG'
      }
    }
  );

  return data;
}
Enter fullscreen mode Exit fullscreen mode

This is the longest query in our api.js file and it looks a little different, so let's review it.

GraphQL fragments

The very first part is called a fragment and it's decorated with the fragment keyword in the query.

`
fragment PostFields on Post {
  title
  excerpt
  slug
  date
  featuredImage {
    node {
      sourceUrl
    }
  }
}
//...rest of query
`
Enter fullscreen mode Exit fullscreen mode

GraphQL fragments give us the ability to break larger, more complex queries into smaller, reusable parts.

For example, you might have a couple of queries as part of your call, but they both use the same Post data. Rather than have to define the same fields on each query, you can define a single fragment, and then use the spread operator syntax to pull those fields in to each separate query.

We've done that here in the PostBySlug query that we defined:

`
query PostBySlug($id: ID!, $idType: PostIdType!) {
  post(id: $id, idType: $idType) {
    ...PostFields
    content
  }
}
`
Enter fullscreen mode Exit fullscreen mode

Notice the ...PostFields fragment that we've referenced. You could also remove the fragment and define the query like this:

`
query PostBySlug($id: ID!, $idType: PostIdType!) {
  post(id: $id, idType: $idType) {
    title
    excerpt
    slug
    date
    featuredImage {
      node {
        sourceUrl
      }
    }
    content
  }
}
`
Enter fullscreen mode Exit fullscreen mode

GraphQL variables

The other interesting thing in our query is the use of variables to filter the specific Post we want to fetch data about.

Focussing on the main part of the query for now, this part:

`
query PostBySlug($id: ID!, $idType: PostIdType!) {
    post(id: $id, idType: $idType) {
      ...PostFields
      content
    }
  }
`,
{
  variables: {
    id: slug,
    idType: 'SLUG'
  }
};
Enter fullscreen mode Exit fullscreen mode

You can see the GraphQL variables defined with a '\$' dollar symbol. In the first line, query PostBySlug($id: ID!, $idType: PostIdType!) we're defining our query name and the variables we'll be passing in, and their types.

The variable types are dictated by the GraphQL schema. You can view the schema on the WordPress GraphiQL explorer, but it's a bit beyond the scope of this article.

Follow me on Twitter @kendalmintcode

Next, we pass those variable placeholders in to filter a single, specific Post item using post(id: $id, idType: $idType).

Of course, now we need to actually pass in the variable values, which is where the second argument of the fetchAPI() method comes in. We pass in a plain JavaScript object with a variables property that contains all our GraphQL variables and their values.

In this case, for id we're using the slug argument passed to the containing function, getPost(slug). And for idType we're using a simple string value of SLUG.

With all our queries defined, tested and verified in WordPress GraphiQL, it's on to the frontend components and pages.

Listing blog posts from WordPress using GraphQL

Now the exciting part: building out the blog listing page! Next.js is built on React, so there shouldn't be too much out of the ordinary here.

Open up the /pages/blog/index.js file and let's kick things off with the imports at the top:

import Head from 'next/head';
import Link from 'next/link';

// data
import { getAllPosts } from '../../lib/api';

// styles
import styles from '../../styles/Home.module.css';
import blogStyles from '../../styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

You can see that we're pulling in the Head and Link components from Next.js (more on Head in a moment), followed by our data handling getAllPosts function. Right after those, we're adding two style module files.

These are essentially modular, component-level CSS files that Next.js gives support for right out of the box. We'll also discuss those in a moment.

Cool, imports done. Next thing is to outline the main Blog component:

const Blog = ({ allPosts: { edges } }) => (
  <div className={styles.container}>
    <Head>
      <title>Blog articles page</title>
      <link rel='icon' href='/favicon.ico' />
    </Head>

    <main className={styles.main}>
      <h1 className={styles.title}>Latest blog articles</h1>
      <hr />
      <section>
        {edges.map(({ node }) => (
          <div className={blogStyles.listitem} key={node.id}>
            <div className={blogStyles.listitem__thumbnail}>
              <figure>
                <img
                  src={node.extraPostInfo.thumbImage.mediaItemUrl}
                  alt={node.title}
                />
              </figure>
            </div>
            <div className={blogStyles.listitem__content}>
              <h2>{node.title}</h2>
              <p>{node.extraPostInfo.authorExcerpt}</p>
              <Link href={`/blog/${node.slug}`}>
                <a>Read more ></a>
              </Link>
            </div>
          </div>
        ))}
      </section>
    </main>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

You'll see we're referencing a specific prop, allPosts using the destructuring syntax. This will be a collection of all available Posts returned from the WordPress GraphQL query we defined earlier.

The allPosts prop is automatically provided to our Blog component via the getStaticProps function that we'll define later in the article.

The <Head></Head> component allows us to define meta data for this page and is a built in Next.js feature, more on this in a moment.

Similarly, the className={styles.main} syntax is how we reference styles from our CSS modules in Next.js. Again, we'll cover that shortly.

The main part of the Blog component is the loop that starts with {edges.map(({ node }) =>. It's not the nicest of naming structures, but we're effectively edges is an array of node items, each node represents a WordPress Post item.

Each node returned from the getAllPosts() API function will be structured similar to this:

{
  "node": {
    "id": "cG9zdDoyOA==",
    "date": "2020-07-09T07:18:42",
    "title": "A third post with an interesting name",
    "slug": "a-third-post-with-an-interesting-name",
    "extraPostInfo": {
        "authorExcerpt": "some excerpt details here",
        "thumbImage": {
        "mediaItemUrl": "http://demo.robkendal.co.uk/wp-content/uploads/2020/07/v7jgc6a3zn951.jpg"
      }
    }
  }
},
Enter fullscreen mode Exit fullscreen mode

Knowing this information, it becomes easier to pull out the relevant bits of content we need and inject them into our React JSX, like this:

{
  edges.map(({ node }) => (
    <div className={blogStyles.listitem} key={node.id}>
      <div className={blogStyles.listitem__thumbnail}>
        <figure>
          <img
            src={node.extraPostInfo.thumbImage.mediaItemUrl}
            alt={node.title}
          />
        </figure>
      </div>
      <div className={blogStyles.listitem__content}>
        <h2>{node.title}</h2>
        <p>{node.extraPostInfo.authorExcerpt}</p>
        <Link href={`/blog/${node.slug}`}>
          <a>Read more ></a>
        </Link>
      </div>
    </div>
  ))
}
Enter fullscreen mode Exit fullscreen mode

Meta data with Next.js Head

If you've built a site with React before you've probably come across the need to add meta data to your page. If you've done that, then there's an equally good chance you've come across React Helmet. React Helmet is a really straightforward means to inject meta data into a page.

Next.js offers a similar option that's handily baked right in. It provides a component called <Head> which you'll see imported at the top of our /pages/blog/index.js page like so:

import Head from 'next/head';
Enter fullscreen mode Exit fullscreen mode

And using it is even easier. Again looking at the top of our Blog component:

<head>
  <title>Blog articles page</title>
  <link rel="icon" href="/favicon.ico" />
</head>
Enter fullscreen mode Exit fullscreen mode

Anything you add between the opening and closing <Head></Head> tags will be magically transported to the <head> of the static output .html file.

Module styling with .module.css files

Next.js offers a range of built in CSS support. One of the most impressive is the modular, component-level CSS support.

You can define a component CSS file by creating a file with the naming convention, [name].module.css and importing it in the component or page you want to use it in.

Then, to apply the component-level styles, you attach them to an element as you would a JavaScript object, e.g. className={styles.class}.

A more complete example might look like this:

import someStyles from 'componentName.module.css';

export default function MyComponent() {
  return (
    <main className={someStyles.aclassname}>
        ...rest of content here
    </main>
  )
}
Enter fullscreen mode Exit fullscreen mode

This applies a unique class name to the component when it's rendered on the page, scoping it to that component so that there are no class conflicts.

Of course, you don't have to use this convention. You can just load in stylesheets as you would normally. However, to avoid conflicts, Next.js dictates that you must do this from a special file called _app.js, which you'll find by default under the /pages/ directory.

With that background in mind, we can populate the /styles/Blog.module.css with some basic styles for the blog list. Open up the file and copy in the following:

.listitem {
  padding: 0.5em 0 1em;
  margin: 1em auto 0.5em;
  display: flex;
  max-width: 60%;
  border-bottom: 1px solid hsl(0, 0%, 89%);
}

.listitem__thumbnail img {
  max-width: 10em;
}

.listitem__content h2 {
  margin-top: 0;
}

.article {
  max-width: 75%;
  margin: 1em auto;
}

.postmeta {
  text-align: center;
  font-size: 1.5rem;
}

.article img {
  max-width: 60%;
  height: auto;
}
Enter fullscreen mode Exit fullscreen mode

It's not super imperative to have these styles in place and feel free to amend them. They do stop things looking a little wild though.

The last thing to do here is the quickly add in some link styles to the /styles/global.css file, so open that up and add the following styles in:

a {
  color: #0070f3;
  text-decoration: none;
}

a:hover,
a:focus,
a:active {
  text-decoration: underline;
}
Enter fullscreen mode Exit fullscreen mode

Static generation and handling external data

Next.js does a fantastic job of fetching data and baking it into your pages at build time. It offers two main ways to grab this data:

  1. Fetching data at build time via getStaticProps() - this is known as static generation.
  2. Fetching data at render time via getServerSideProps() - this is known as server side rendering or SSR

There is a third, sort of hybrid way, which is where you have a static-generated front end that calls for data once the page has rendered. It's called client-side rendering. It's not exactly an edge case, but it offers a mix of the main two methods mentioned above and might be used on, say, a dashboard page.

Most of the time, you'll want to strive for static generation using getStaticProps() because it offers the best performance for the end user and really takes advantage of the whole Jamstack, static site generation approach.

This is especially key if we're using WordPress because WordPress is already a server-side rendered website out of the box. Part of the reason to decouple WordPress from its own frontend with Next.js is to remove this server business and statically generate our frontend website.

If you're unsure which approach to take you can ask the question: "Can this page be pre-rendered ahead of a user's request?" If your answer is 'yes', then static generation is the right choice.

Accessing external data in Next.js using getStaticProps()

Now that we're a bit clearer on Next.js and its static generation with external data-fetching, we can implement Next's getStaticProps method in our /blog/index.js page.

Add the following implementation of getStaticProps underneath the default export of our Blog component:

export async function getStaticProps() {
  const allPosts = await getAllPosts();
  return {
    props: {
      allPosts
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

And just how simple is that?! This function will be called by Next.js during build time to fetch our data from WordPress, passing it into the props of our main Blog component.

You could absolutely do all the data fetching right here without issue. However, we've abstracted at lot of the grunt work into our /lib/api.js for several reasons:

  1. It reduces the length of our components.
  2. It abstracts the data handling responsibility away from the component (whose job isn't really fetching data).
  3. It cleans up our components, making them much more maintainable and readable.
  4. It reduces duplication, especially around the main fetchAPI() function.

It's important to remember that getStaticProps() has to be named exactly so. It also has to return a props: {} object.

You can read more about static generation and getStaticProps() in the official Next.js documentation.

Checking the output

Let's spin up our site to check how things are looking so far. Fire up the console and type:

yarn dev
Enter fullscreen mode Exit fullscreen mode

This will start the local development server and you'll see just how fast Next.js is able to build our pages and have our local site ready to preview on http://localhost:3000.

You should get a view like this one:

Next.hs home page running on the local development server

If you click on the 'blog articles page` link right there under the title, you should see a page that looks like this:

Blog listing page running on our local development server with data pulled from our headless CMS instance of WordPress

Of course, if you've used your own WordPress instance for your headless CMS with different data and properties, then it's going to look potentially very different. But you get the idea!

Handling dynamic routes like blog posts

Excellent. We've made it this far, and we're almost done. Now, we need to complete the circle and handle the routing when someone clicks on our 'Read more >' links we have on our blog listing page.

Right now, if you click them, you're probably going to see an error or a 404 page or some otherwise less desirable result.

Up until now, we've been handling known, static routes β€” pages that have been explicitly defined ahead of time with fixed endpoints.

However, with our blog detail page (i.e. the one that will handle the individual blog article's content), we have an unknown number of these with URLs (i.e. 'slugs') that we also don't know ahead of time.

That's where dynamic routing comes in with Next.js. We've already seen what that looks like earlier in this very article, and I'd recommend reviewing the excellent documentation on dynamic routing from Next.js themselves.

The basic process for dynamic routing

In order to handle the dynamic route for our blog article page, we need to do four things:

  1. Define a dynamic page to handle the route (we've done this with /pages/blog/[slug].js).
  2. Create and export a default component within this page to actually handle the data and display some output.
  3. Implement the getStaticProps function from Next.js as we've already done for the listing page. This will handle fetching the data for a single blog article.
  4. Implement the getStaticPaths function from Next.js. This is another special function that we use for dynamic pages that fetches a list of possible matches for our route so that the correct HTML pages can be created at build time.

Let's fill out the blog article page now.

Building out the blog detail page

Open up the dynamic blog article page and paste in the following code, which we're walk through next.

(NOTE: I had to switch to images for the code here because Dev's syntax highlighting crapped out...apologies for that)

Alt Text

Alt Text

Let's break down each section, so you understand what's happening and why.

Imports for the blog article page

We have a few more imports in this file, as follows:

Alt Text

Nothing too unfamiliar here: we're importing our data fetching utilities from the api.js file and some styles using the CSS module approach we discussed earlier.

We're also pulling in the Head and Link components from Next.js itself so that we can updated the meta data and provide a link back to the main articles listing page.

The new import we've introduced is the useRouter from the next/router library. As we've seen, Next.js provides its own built-in router functionality, most of which is handled behind the scenes and you don't need to get involved with. However, occasionally you'll need to tap into the routing powers and that's where useRouter comes in.

We're going to use it to handle a routing fallback situation.

There's loads of quality documentation on Next's router feature on the official docs.

Next.js router and date formatting

Next up, we have a few small bits of logic at the beginning of our Post component:

Alt Text

The formatDate function should be quite clear and just transforms the rather ugly date string that WordPress provides us into a more human readable format.

The interesting part here is the useRouter() Hook. We define an instance of the useRouter Hook into a variable, router. Then, we can do a simple error handling escape in this line, if (!router.isFallback && !postData?.slug).

What's happening here, is that we're looking at the isFallback property to determine if this page being rendered is a fallback version ( we're going to cover this in a moment ) and if it's not, but we also don't have a slug, then this means we won't be able to render a page for this route.

Instead of showing a horrible error page, we're going to return a simple paragraph with an error message in.

Note: this probably needs a more fleshed out solution for a production environment, but this is the foundation of how we'd handle the error when a dynamic route cannot be found.

Providing a fallback in the main content

If a page isn't quite generated, then we can optionally provide a fallback page or piece of content whilst getStaticProps finishes running and generating the page.

That's what's going on in this slice of our main component's return method:

Alt Text

If our router object has an isFallback property set to true, then we'll show a simple heading with a loading message until getStaticProps has finished and our content is ready.

Finding the right article with getStaticPaths()

With the main Blog component defined, we need to add in Next's getStaticPaths method as an exported async function. This will run at build time and create our static HTML pages, one for each blog article that it finds.

Add the following code at the bottom of the /pages/blog/[slug].js file:

Alt Text

To begin with, we call the getAllPostsWithSlug() from our api.js file. This will return us a rather clunky set of JSON data that includes WordPress Post slugs as node items, wrapped in an edges array.

That's fine and dandy but we need our slugs to match our site's blog post URL format, /blog/some-blog-article-slug.

To achieve this, we can run a map function to produce an array of URL strings that matches this preferred format.

Finally, we're also adding in a fallback: true property, which Next.js will automatically inject into its router and make it available via the useRouter Hook we looked at previously.

Fetching the article data from WordPress and GraphQL via getStaticProps()

The last piece of this data-fetching puzzle is to add the same getStaticProps function to the blog article page that we did to the blog listing page.

We'll change it slightly so that we obviously fetch the individual Post data, rather than a list of blog articles, so add the following at the end of the /pages/blog/[slug].js file:

Alt Text

The main addition here is that we're pulling in the params argument which is destructured from the default context object that Next.js provides to the getStaticProps method.

The params object contains the route parameters for pages using dynamic routes. In our case, because our dynamic route is [slug], we can refer to this parameter as params.slug as you can see we're doing here.

Similarly, had we called our page [id], we would have referred to this parameter via params.id.

Running the website locally

So, with all of that in place, let's fire up the dev server again and test things out. Bring up the terminal and type in the dev server command:

yarn dev

Navigate to http://localhost:3000 and view the blog listing page. Now when you click on one of the 'Read more >' links, you'll be taken to a blog article page, dynamically routed, which should look something like this:

Again, your results may vary depending on your chosen styling and where you've pulled your data from.

What's next in the series

Coming up next, in part three, we'll be creating an XML RSS feed as part of the deployment process so that we can publish and syndicate our posts across the web.

This is a common feature on just about any blog out there on the web, but it's not as straightforward as you may think when using Next.js. Don't worry though, it's all covered in part three.

If you like this article, you'll love the other helpful content I post on Twitter. Find me on Twitter @kendalmintcode and say hi.

Top comments (33)

Collapse
 
spencersmb profile image
spencer

This write up is super detailed and an awesome source of info thanks for sharing. I have one basic question. When I want to publish a new post or page from Wordpress, normally I just hit publish and it would show up when I refresh the blog posts index page on my server based wordpress site. What happens with next.js? What would I need to do in order to publish posts weekly using the Wordpress scheduler for example? Would the new post just show up in next.js when I hit refresh? I'm guessing not since its statically built.

Thanks again,
Spencer

Collapse
 
imakethesites profile image
Mark Wlodawski

Have you solved this yet? I need to publish more posts without touching my Next app.

Collapse
 
spencersmb profile image
spencer

I've only read that you can have wordpress fire a webhook that will tell Next.js to rebuild when you publish a new post. I read that from a post that was about a year old, so maybe there is a newer more streamlined way.

Thread Thread
 
imakethesites profile image
Mark Wlodawski

Dang, this might've been important to make note of in the article. Clearly I'm not an active blogger, as I built and published my blog a couple of months ago and I'm just now getting around to asking this, but I guess it's a learning experience.

Thread Thread
 
spencersmb profile image
spencer • Edited

Yea after reading @AndrewRoss comment below, It sounds like the future proof solution to git/webhooks. I would have a look through that.

Thread Thread
 
imakethesites profile image
Mark Wlodawski

Thanks, I'll check it out.

Thread Thread
 
logantai24 profile image
Logan Tai

If you are hosting your NextJS site in vercel/netlify, the incremental static generation feature should be able to facilitate your needs as the page will be built again in the background when a new request comes in, you only need to add a simple " revalidate: 1" in your getStaticProps function

nextjs.org/docs/basic-features/dat...

Thread Thread
 
imakethesites profile image
Mark Wlodawski

I am hosting on netlify, and I've been hoping there would be a faster solution than adding Apollo. I'll check this out, thanks.

Thread Thread
 
imakethesites profile image
Mark Wlodawski

@logantai24 Thanks, it worked!

Collapse
 
samuelkobe profile image
Samuel Kobe • Edited

Hello, thank you for writing such a detailed Article. I am having trouble getting up and running after adding the new directories and files above. When trying to view the posts on the /blog page at the step titled 'Checking the output' I am getting the following error after following the "blog articles page" link.

Screenshot: dev-to-uploads.s3.amazonaws.com/i/...

EDIT1:

I was able to get it working buy adding

export default Blog
Enter fullscreen mode Exit fullscreen mode

before

export async function getStaticProps() {
  const allPosts = await getAllPosts();
  return {
    props: {
      allPosts
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Is this correct?

Collapse
 
setoelkahfi profile image
Seto

This is correct.

Collapse
 
imakethesites profile image
Mark Wlodawski

Thanks for this article, I've learned quite a bit!

Here are some details that might have seemed obvious to most people, but took work and mysterious errors for me to figure out:

  1. After editing your .env file by entering the WP URL, if your localhost had been running, close and restart it. Otherwise, you'll receive an error saying that actual URLs must be entered.
  2. After entering your GraphQL query into api.js, don't be surprised when you see an error saying that thumbnail can't return null from blog/index.js if you haven't populated each of the queried fields (author excerpt and thumbnail image) inside your post screen on the WordPress site.

Both of these were because I was using my own WordPress instance, maybe people who were using your information while following along didn't receive the errors.

Thanks again!

Collapse
 
trahulsam1997 profile image
Rahul

Thanks for this mate! I actually had the issue with the 2nd point.

Collapse
 
asross311 profile image
Andrew Ross

Great article Rob! I've been using Headless Wordpress for a couple of months now and thought I should mention that one of my absolute favorite features is stable "revalidate" on getStaticProps. Background incremental static regeneration is a game changer, seamlessly threading the headless WP server with the client in production. No githooks required

// ...
export const getStaticProps = async ({
    preview = false,
    // context,
    field = MODIFIED || TITLE || DATE,
    order = ASC || DESC,
    desiredCategory
}: StaticProps) => {
    // console.log(context);
    const allPosts = await getAllPostsForHomeAlphabetical({
        preview,
        field,
        order
    });
    const tagsAndPosts = await getTagAndPosts();
    const categories = await getCategories();

    // const apolloClient: ApolloClient<NormalizedCacheObject> = initializeApollo();

    // await apolloClient.query({
    //  query: ALL_POSTS_QUERY,
    //  variables: allPostsQueryVars
    // });
    // const userOptions = await getAllPostsForHomeSorted(preview, field);
    // IMPORTANT https://nextjs.org/blog/next-9-5#stable-incremental-static-regeneration
    return {
        props: {
            // initialApolloState: apolloClient.cache.extract(),
            allPosts,
            preview,
            tagsAndPosts,
            field,
            order,
            categories
        },
        revalidate: 1
    };
};

// ...
Enter fullscreen mode Exit fullscreen mode

(transitioning to @apollo/client is a work in progress)

nextjs.org/blog/next-9-5#stable-in...

Collapse
 
kendalmintcode profile image
Rob Kendal {{β˜•}}

Thanks Andrew and so pleased you like the article. Yes, thanks for highlighting this. It's a feature that's definitely worth people checking out as it is indeed a game changer.

Collapse
 
lowbin profile image
lowbin • Edited

First of all, thank you for this wonderful tutorial. I did my local installation connected to a wordpress installed on my server at Godaddy. When calling the blog page displays the error that is in the print.
I had a similar error in WP-headless with Gatsby locally did not display the Single Posts, but when I deployed on Netilify the posts were called.
Can you help me please?

Erro:
);
45 |

46 | export async function getAllPosts(preview) {
| ^
47 | const data = await fetchAPI(
48 | `
49 | query AllPosts {

Collapse
 
pasqat profile image
Pasquale

Great series to fast test WPgraphql and Nextjs. I have just one question.
Is there a way manipulate data set with dangerouslySetInnerHtml?

For expamle in the article is present a slider of image, on Next that slider will not be rendered properly (the gallery of gutemberg editor is show as a simple list)...

Collapse
 
lowbin profile image
lowbin • Edited

First of all, thank you for this wonderful tutorial. I did my local installation connected to a wordpress installed on my server at Godaddy. When calling the blog page displays the error that is in the print.
I had a similar error in WP-headless with Gatsby locally did not display the Single Posts, but when I deployed on Netilify the posts were called.
Can you help me please?

Collapse
 
zackrosegithub profile image
Zack Rose

Hi there,

when I go to my site/graphql I get the following error:

GraphQL Request must include at least one of those two parameters: \"query\" or \"queryId

Does anyone know why? I can't really go any further until I can access the data!

Thank you

Collapse
 
brentonjackson profile image
Brenton

Do it like this:

{sitename.com}/graphql?query={stuff that's in your graphiql query}

For example, to get all posts of a specific category:
query={category(id: "asdfh3e2"){posts{nodes{title}}}}

Play around with the GraphiQL IDE to find out the queries that go inside /graphql?query={}

Collapse
 
justintrang profile image
Justin Trang

I had same error before because I use GET in stead of POST.
If you use POST, then you should recheck your query string ( must start with: query ... )

Collapse
 
a007mr profile image
a007mr

Outstanding article! Thank you so much Rob for this detailed guide.

I did everything according to you blog and it works great on the local machine. I can read articles. However, I have a problem when I deploy it to vercel. It shows an error during the build time:

> Build error occurred
TypeError: Only absolute URLs are supported
    at getNodeRequestOptions (/vercel/path0/node_modules/next/dist/compiled/node-fetch/index.js:1:63538)
    at /vercel/path0/node_modules/next/dist/compiled/node-fetch/index.js:1:65069
    at new Promise (<anonymous>)
    at Function.fetch [as default] (/vercel/path0/node_modules/next/dist/compiled/node-fetch/index.js:1:65003)
    at fetchWithAgent (/vercel/path0/node_modules/next/dist/server/node-polyfill-fetch.js:53:39)
    at fetchAPI (/vercel/path0/.next/server/chunks/963.js:69:23)
    at getAllPostsWithSlug (/vercel/path0/.next/server/chunks/963.js:141:24)
    at getStaticPaths (/vercel/path0/.next/server/pages/blog/[slug].js:171:63)
    at buildStaticPaths (/vercel/path0/node_modules/next/dist/build/utils.js:487:37)
    at /vercel/path0/node_modules/next/dist/build/utils.js:622:121 {
  type: 'TypeError'
}
Error: Command "npm run build" exited with 1
Enter fullscreen mode Exit fullscreen mode

And my getStaticPaths function looks like you described it:

export async function getStaticPaths() {
  const allPosts = await getAllPostsWithSlug();

  return {
    paths: allPosts.edges.map(({ node }) => `/blog/${node.slug}`) || [],
    fallback: true
  };
}
Enter fullscreen mode Exit fullscreen mode

What might be wrong with deployment if it works fine locally and I can build it locally?

Collapse
 
a007mr profile image
a007mr • Edited

Ohh, I forgot to add global env for WP blog hehe. It works now. Thank you so much for article. It's brilliant!

Collapse
 
bsharptechnology profile image
bsharptechnology

This is a great article. Thanks for sharing. I originally used Next.js for my website it was so fast and snappy. But then I got sick of managing the content manually, so I move to WordPress. I love WordPress for its ability to build pages and content easily, but I have missed the Next.js install for its performance. Now I'm looking to combine the two to get the best of both worlds. Your guide has been great in helping achieve this.

A little problem though. I note the output for the post/page content includes shortcodes. Is there a way to deal with these and turn them into plain HTML for use in Next.js? I use the DIVI theme on my site now and it builds great looking pages, so it would be helpful to convert these to HTML and reuse them.

Collapse
 
robmarshall profile image
Robert Marshall

I came across this issue with shortcodes myself. I am not sure if this is exactly what you means, but it allows me to turn shortcode style snippets into NextJS React Components: robertmarshall.dev/blog/using-word...

Collapse
 
crisomg profile image
Cristian

Hola Ron, Excelente articulo! Todo me funcionΓ³ a la primera :D.

estuve investigando un poco y tengo una duda ΒΏPor que usar la propiedad "dangerouslySetInnerHTML"? ΒΏEs recomendable en este caso? ΒΏeso no dejarΓ­a una vulnerabilidad del tipo "xss"? en tal caso ΒΏQue nos recomendarΓ­as?

Collapse
 
varodesign profile image
VaroDesign

if your URL yourlink.com/graphql/ is getting a 404 error and you can't run your content on the blog page. Try to change your WordPress Settings > Permalinks to "Post name"

Collapse
 
pudpark profile image
Paul Barron

Has anyone run into this error?
Error: Failed to fetch API
I get a status 200 but nothing is returned.

Collapse
 
pudpark profile image
Paul Barron

Never mind. I t turns out that I was querying for the extraPostInfo but I never made in ACF. πŸ˜³πŸ˜…

Collapse
 
tomhermans profile image
tom hermans

Wow. Great article. Done a similar thing with Nuxt and Gatsby, so a lot of familiar principles here. Since I'm exploring Nextjs now, will give this a go.

Collapse
 
kendalmintcode profile image
Rob Kendal {{β˜•}}

Thanks Tom, best of luck with your project :D

Collapse
 
ilirbajrami_ profile image
Ilir Bajrami

how can we add pagination to the blog page and "Next" "Previous" buttons to single posts?