<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fran Agulto</title>
    <description>The latest articles on DEV Community by Fran Agulto (@franadev).</description>
    <link>https://dev.to/franadev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F783183%2Fc0be1278-0900-4479-a9bd-7221a8e55fa0.jpeg</url>
      <title>DEV Community: Fran Agulto</title>
      <link>https://dev.to/franadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/franadev"/>
    <language>en</language>
    <item>
      <title>Using Fragments with WPGraphQL in Faust.js</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Thu, 20 Jul 2023 21:08:07 +0000</pubDate>
      <link>https://dev.to/franadev/using-fragments-with-wpgraphql-in-faustjs-2blm</link>
      <guid>https://dev.to/franadev/using-fragments-with-wpgraphql-in-faustjs-2blm</guid>
      <description>&lt;p&gt;In WPGraphQL, &lt;a href="https://graphql.org/learn/queries/#fragments"&gt;fragments are a feature of GraphQL in the GraphQL query language&lt;/a&gt;that allows you to define reusable selections of fields. Fragments allow you to group fields together and give them a name, which can then be used in multiple queries or mutations.&lt;/p&gt;

&lt;p&gt;In this article, I will explain what they provide and how to use them in Faust.js. Even though we’re using Faust.js, you should be able to transfer this knowledge to any other using WPGraphQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To benefit from this post, you should be familiar with the basics of WordPress development, &lt;a href="https://www.wpgraphql.com/"&gt;WPGraphQL&lt;/a&gt;, &lt;a href="https://faustjs.org/"&gt;Faust.js&lt;/a&gt;, and the &lt;a href="https://www.apollographql.com/docs/react/"&gt;Apollo Client&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps for Local Development Set-up
&lt;/h3&gt;

&lt;h4&gt;
  
  
  WordPress Setup:
&lt;/h4&gt;

&lt;p&gt;Set up a WordPress site on local, WP Engine, or any host of your choice&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Set up a WordPress site on local, WP Engine, or any host of your choice&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install and activate the WPGraphQL plugin&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install and activate the &lt;a href="https://wordpress.org/plugins/faustwp/"&gt;Faust.js plugin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install and activate the &lt;a href="https://www.advancedcustomfields.com/"&gt;Advanced Custom Fields plugin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install and activate the &lt;a href="https://github.com/wp-graphql/wpgraphql-acf"&gt;WPGraphQL For Advanced custom fields plugin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Faust.js Setup
&lt;/h4&gt;

&lt;p&gt;Please follow the instructions to set up Faust.js in the &lt;a href="https://faustjs.org/tutorial/get-started-with-faust#quick-start"&gt;quickstart portion of the docs here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits of Using Fragments
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code Reusability&lt;/strong&gt;: Fragments enable you to define a set of fields once and reuse them across different queries or mutations. This helps in reducing code duplication and promotes a modular approach to defining GraphQL operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query Organization&lt;/strong&gt;: Fragments help organize complex queries by breaking them into smaller, more manageable parts. You can efficiently compose queries and maintain a clear structure by defining fragments for specific data structures or entities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readability and Maintainability&lt;/strong&gt;: Fragments make GraphQL queries more readable and understandable. Separating the fields into reusable fragments makes it easier to grasp the structure of the data being queried. Additionally, when changes are required, you can update the fragment definition, and all the queries using that fragment will be automatically updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create and Implement Fragments for Your WPGraphQL Queries
&lt;/h2&gt;

&lt;p&gt;Now that we went over the benefits of using fragments, let’s create some.&lt;/p&gt;

&lt;p&gt;In this example, I have a custom post type called &lt;code&gt;Movies&lt;/code&gt; with the fields &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;slug&lt;/code&gt; and &lt;code&gt;uri&lt;/code&gt; that we want to request.&lt;br&gt;
&lt;br&gt;
 &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  movies {
    nodes {
      title
      id
      slug
      uri
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The above code is a WPGraphQL query with no fragments.  &lt;/p&gt;

&lt;p&gt;This is what it would look like if we made a fragment for the above query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  movies {
    nodes {
      ...MovieFragment
    }
  }
}

fragment MovieFragment on Movie {
  title
  id
  slug
  uri
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down how we created a fragment for our query.&lt;/p&gt;

&lt;p&gt;Once we know what fields we want to pull out from our &lt;code&gt;Movies&lt;/code&gt; custom post type, we start making our fragment with the keyword &lt;code&gt;fragment&lt;/code&gt; followed by the name we choose, in this case, we call it &lt;code&gt;MovieFragment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;on Movie&lt;/code&gt; clause specifies this fragment is applicable to the Movie object type.  When WPGraphQL returns a &lt;code&gt;node&lt;/code&gt; of type &lt;code&gt;Movie&lt;/code&gt;, it uses this fragment to determine what fields to return on the result.&lt;/p&gt;

&lt;p&gt;Inside the fragment, there are several fields (&lt;code&gt;title, id, slug, uri&lt;/code&gt;) that correspond to the fields of the Movie type.&lt;/p&gt;

&lt;p&gt;Lastly, we take our fragment named &lt;code&gt;MovieFragment&lt;/code&gt; and insert it into the main query block right under the &lt;code&gt;nodes&lt;/code&gt; that the original query had with the fields instead of the actual fields and just precede it by using &lt;code&gt;...&lt;/code&gt; much like the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"&gt;JavaScript spread syntax&lt;/a&gt; followed by the name. &lt;/p&gt;

&lt;p&gt;You can go to your WP Admin, navigate over to &lt;strong&gt;GraphiQL IDE&lt;/strong&gt;, and create the same custom post type with the same fields.  Copy my queries and try this yourself or go ahead and make your own.&lt;/p&gt;

&lt;p&gt;Now that this is done, we get back the same results, but our code is much cleaner and organized in a reusable way. &lt;/p&gt;

&lt;h2&gt;
  
  
  Using Fragments in Faust.js
&lt;/h2&gt;

&lt;p&gt;Now that we know how to make and implement fragments, let’s explore how Faust.js uses them out of the box with its default examples and create some custom ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default Faust.js Fragments
&lt;/h3&gt;

&lt;p&gt;The first fragments we will explore are one of the defaults that come with Faust.js.  Let’s look at the front page template that renders when you visit the main path.  This file is located at &lt;code&gt;wp-templates/front-page.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useQuery, gql } from "@apollo/client";
import * as MENUS from "../constants/menus";
import { BlogInfoFragment } from "../fragments/GeneralSettings";
import {
  Header,
  Footer,
  Main,
  Container,
  NavigationMenu,
  Hero,
  SEO,
} from "../components";

export default function Component() {
  const { data } = useQuery(Component.query, {
    variables: Component.variables(),
  });

  const { title: siteTitle, description: siteDescription } =
    data?.generalSettings;
  const primaryMenu = data?.headerMenuItems?.nodes ?? [];
  const footerMenu = data?.footerMenuItems?.nodes ?? [];

  return (
    &amp;lt;&amp;gt;
      &amp;lt;SEO title={siteTitle} description={siteDescription} /&amp;gt;
      &amp;lt;Header
        title={siteTitle}
        description={siteDescription}
        menuItems={primaryMenu}
      /&amp;gt;
      &amp;lt;Main&amp;gt;
        &amp;lt;Container&amp;gt;
          &amp;lt;Hero title={"Fran's Front Page"} /&amp;gt;
          &amp;lt;div className="text-center"&amp;gt;
            &amp;lt;p&amp;gt;This page is utilizing the "front-page" WordPress template.&amp;lt;/p&amp;gt;
            &amp;lt;code&amp;gt;./wp-templates/front-page.js&amp;lt;/code&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/Container&amp;gt;
      &amp;lt;/Main&amp;gt;
      &amp;lt;Footer title={siteTitle} menuItems={footerMenu} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  query GetPageData(
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
  ) {
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;

Component.variables = () =&amp;gt; {
  return {
    headerLocation: MENUS.PRIMARY_LOCATION,
    footerLocation: MENUS.FOOTER_LOCATION,
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the very top of this file, we import the fragment we will use named &lt;code&gt;BlogInfoFragment&lt;/code&gt; from the fragments directory.  The next thing to focus on is the bottom of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  query GetPageData(
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
  ) {
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the template &lt;code&gt;Component,&lt;/code&gt; we define a query property that we assign at the top of the query block is the &lt;code&gt;Component.query&lt;/code&gt; syntax, a GraphQL query string used to fetch the template’s data in the &lt;a href="https://faustjs.org/reference/template-system"&gt;Faust.js template hierarchy&lt;/a&gt;. This is a Faust.js convention that makes it easier to load data.&lt;/p&gt;

&lt;p&gt;Next, we pass in our fragment, which contains the fields from the WP &lt;code&gt;GenralSetttings&lt;/code&gt; object type of title and description.   Notice the syntax being &lt;code&gt;${BlogInfoFragment}&lt;/code&gt;. This is &lt;a href="https://graphql.org/learn/queries/#variables"&gt;a technique called string interpolation or template interpolation&lt;/a&gt;. It allows you to dynamically insert the content of the fragment variable into the query string at that specific location. This is equivalent to defining the fragment inline, which allows us to use the fragment in the actual query operation.&lt;/p&gt;

&lt;p&gt;In this case, the &lt;code&gt;${BlogInfoFragment}&lt;/code&gt; is used to include the fragment definition within the query string. The Apollo Client uses the &lt;code&gt;gql&lt;/code&gt; function to parse the query string and understand that the &lt;code&gt;...BlogInfoFragment&lt;/code&gt; syntax is referencing the fragment named &lt;code&gt;BlogInfoFragment&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fragments Directory
&lt;/h3&gt;

&lt;p&gt;This fragment is located at &lt;code&gt;fragments/GeneralSettings.js&lt;/code&gt; and is imported at the top of this file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GeneralSettings.js&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql } from '@apollo/client';


export const BlogInfoFragment = gql`
fragment BlogInfoFragment on GeneralSettings {
title
description
}
`;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code block, we export the variable called BlogInfoFragment and use the &lt;code&gt;gql&lt;/code&gt; client function from Apollo to define it.  Then we create the fragment in the template literal and add the fields we want back in the fragment. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates"&gt;In GraphQL, the template literal&lt;/a&gt; starts with the backtick character &lt;code&gt;(&lt;/code&gt;&lt;code&gt;)&lt;/code&gt; and ends with another backtick. They are used to define multi-string values for queries and mutations. This is used to define the GraphQL fragment named &lt;code&gt;BlogInfoFragment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the directory in Faust.js which contains your fragment files that can be shared and reused when you import them into your components and queries.&lt;/p&gt;

&lt;p&gt;The fragment directory is useful when you need a centralized location for all your fragments. This can be beneficial when multiple components share the same fragments or when you want to keep your component files less cluttered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Colocated Fragments
&lt;/h3&gt;

&lt;p&gt;Colocated fragments are what Faust.js uses in the majority of its components. Colocation refers to the practice of defining fragments in the same file or module as the component that uses them, keeping the fragment definition alongside the component’s code.&lt;/p&gt;

&lt;p&gt;This is what we consider best practice within Faust.js for these reasons:&lt;/p&gt;

&lt;p&gt;Code Organization: By colocating fragments in the same component that uses them, the code becomes more organized and maintainable.&lt;/p&gt;

&lt;p&gt;Local Scope: Fragments defined within a component have local scope and are not exposed globally. This prevents naming collisions and reduces the likelihood of conflicts with other fragments defined elsewhere in the application.&lt;/p&gt;

&lt;p&gt;Explicit Relationship: Colocated fragments establish an explicit relationship between the fragment and the component that uses it. When reading or modifying the component, developers understand the specific fragment(s) used, facilitating more manageable maintenance and updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Things to Consider/Best Practices
&lt;/h3&gt;

&lt;p&gt;You could run into conflicts if you had two fragments with the same name, but different fields, it would cause an issue. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Component A
fragment PostFields on Post {
  title
}

# Component B
fragment PostFields on Post {
  id
}

query {
  posts {
    nodes {
      ...PostFields
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the query is using the &lt;code&gt;PostFields&lt;/code&gt; fragment, but WPGraphQL does not know which version of &lt;code&gt;PostFields&lt;/code&gt; to use since there are two with the same name. This results in an invalid query that will not execute.&lt;/p&gt;

&lt;p&gt;This example is valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fragment PostTitle on Post {
  title
}

fragment PostId on Post {
  id
}

{
  posts {
    nodes {
      ...PostTitle
      ...PostId
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We give a unique name to our fragments ensuring each component’s fragment definition remains distinct and can be used without conflict in WPGraphQL operations.&lt;/p&gt;

&lt;p&gt;We can even ask for the same fields in both, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fragment SomeComponentOnTheTopOfThePage on Post {
  id
  title
  author {
    node {
      id
      name
    }
  }
}

fragment SomeComponentOnTheSidebar on Post {
  id
  title
  date
  featuredImage {
    node {
      id
      sourceUrl
    }
  }
}

{
  posts {
    nodes {
      ...SomeComponentOnTheTopOfThePage
      ...SomeComponentOnTheSidebar
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both components need &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; so they both declare it. This might seem redundant but it is not. If one component no longer needs the title, it can take it out of its fragment, but the title will still be queried because the other fragment still asks for it.&lt;/p&gt;

&lt;p&gt;Each component should query for what it specifically needs. However, components should not ask for fields they don’t actually need to satisfy another component’s needs.&lt;/p&gt;

&lt;p&gt;Let’s shift our focus back to our &lt;code&gt;wp-templates/front-page.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  query GetPageData(
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
  ) {
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s look at the bottom of the file. The colocated fragment is&lt;code&gt;${NavigationMenu.fragments.entry}&lt;/code&gt;.  The &lt;code&gt;${NavigationMenu.fragments.entry}&lt;/code&gt; syntax includes the entry fragment from the &lt;code&gt;NavigationMenu&lt;/code&gt; fragment collection. It allows the fields defined in the entry fragment to be used in the query.&lt;/p&gt;

&lt;p&gt;This fragment is part of the &lt;code&gt;NavigationMenu&lt;/code&gt; object and is defined in the &lt;code&gt;components/NavigationMenu.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import classNames from 'classnames/bind';
import { gql } from '@apollo/client';
import Link from 'next/link';
import flatListToHierarchical from '../../utilities/flatListToHierarchical';
import styles from './NavigationMenu.module.scss';
import stylesFromWP from './NavigationMenuClassesFromWP.module.scss';

let cx = classNames.bind(styles);
let cxFromWp = classNames.bind(stylesFromWP);

export default function NavigationMenu({ menuItems, className }) {
  if (!menuItems) {
    return null;
  }

  // Based on https://www.wpgraphql.com/docs/menus/#hierarchical-data
  const hierarchicalMenuItems = flatListToHierarchical(menuItems);

  function renderMenu(items) {
    return (
      &amp;lt;ul className={cx('menu')}&amp;gt;
        {items.map((item) =&amp;gt; {
          const { id, path, label, children, cssClasses } = item;

          // @TODO - Remove guard clause after ghost menu items are no longer appended to array.
          if (!item.hasOwnProperty('__typename')) {
            return null;
          }

          return (
            &amp;lt;li key={id} className={cxFromWp(cssClasses)}&amp;gt;
              &amp;lt;Link href={path ?? ''}&amp;gt;{label ?? ''}&amp;lt;/Link&amp;gt;
              {children.length ? renderMenu(children, true) : null}
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
    );
  }

  return (
    &amp;lt;nav
      className={cx(['component', className])}
      role="navigation"
      aria-label={`${menuItems[0]?.menu?.node?.name} menu`}&amp;gt;
      {renderMenu(hierarchicalMenuItems)}
    &amp;lt;/nav&amp;gt;
  );
}

NavigationMenu.fragments = {
  entry: gql`
    fragment NavigationMenuItemFragment on MenuItem {
      id
      path
      label
      parentId
      cssClasses
      menu {
        node {
          name
        }
      }
    }
  `,
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the very bottom of the file are the fields we ask for in the fragment as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; fragment NavigationMenuItemFragment on MenuItem {
      id
      path
      label
      parentId
      cssClasses
      menu {
        node {
          name
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual fragment is defined in the child component &lt;code&gt;(NavigationMenu.js)&lt;/code&gt; that uses it explicitly within its component. It is then reused into a parent component &lt;code&gt;(front-page.js)&lt;/code&gt;that consumes it and renders it along with other fragments in the query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Fragment Examples
&lt;/h2&gt;

&lt;p&gt;Now that we have an example of default fragments in Faust.js, let’s explore some custom ones in this repo. &lt;/p&gt;

&lt;p&gt;Using the custom post types we made for this article, let’s create a query block and fragments for them.&lt;/p&gt;

&lt;p&gt;The file we are looking at is located at &lt;code&gt;components/MovieCard.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql, useQuery } from "@apollo/client";
import Link from "next/link";

const GET_MOVIES = gql`
  query GET_MOVIES {
    movies {
      nodes {
        ...MovieFragment
      }
    }
    actors {
      nodes {
        ...ActorFragment
      }
    }
  }

  fragment MovieFragment on Movie {
    title
    id
    slug
    uri
  }

  fragment ActorFragment on Actor {
    title
    id
    slug
    uri
  }
`;

function MovieList() {
  const { loading, error, data } = useQuery(GET_MOVIES);

  if (loading) return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  if (error) return &amp;lt;p&amp;gt;Error: {error.message}&amp;lt;/p&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Movies&amp;lt;/h2&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.movies.nodes.map((movie) =&amp;gt; (
          &amp;lt;li key={movie.id}&amp;gt;
            &amp;lt;Link href={`${movie.uri}`}&amp;gt;
              &amp;lt;a&amp;gt;{movie.title}&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;

      &amp;lt;h2&amp;gt;Actors&amp;lt;/h2&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.actors.nodes.map((actor) =&amp;gt; (
          &amp;lt;li key={actor.id}&amp;gt;
            &amp;lt;Link href={`${actor.uri}`}&amp;gt;
              &amp;lt;a&amp;gt;{actor.title}&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default MovieList;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s focus on the top of the file where our WPGraphQL Query and Fragment is located:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const GET_MOVIES = gql`
  query GET_MOVIES {
    movies {
      nodes {
        ...MovieFragment
      }
    }
    actors {
      nodes {
        ...ActorFragment
      }
    }
  }

  fragment MovieFragment on Movie {
    title
    id
    slug
    uri
  }

  fragment ActorFragment on Actor {
    title
    id
    slug
    uri
  }
`;



fragment ActorFragment on Actor {
title
id
slug
uri
}
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MovieFragment&lt;/code&gt; and &lt;code&gt;ActorFragment&lt;/code&gt; are defined inline within the GraphQL query using the &lt;code&gt;fragment&lt;/code&gt; keyword. They are then referenced using the spread syntax &lt;code&gt;(...)&lt;/code&gt; within the &lt;code&gt;GET_MOVIES&lt;/code&gt; query to include the fragment fields in the response which are at the bottom of the query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested Fragments
&lt;/h2&gt;

&lt;p&gt;We not only included the Movie data in a fragment, but we also nested the Actor data fragment in the query block.  This is useful for larger applications where you could have multiple nested fragments that go on and on.&lt;/p&gt;

&lt;p&gt;Along with the main benefits of regular fragments, nested fragments also allow developers to have composition. This enables you to build up more complex fragments by including smaller fragments within them. You can nest fragments within other fragments, creating a hierarchy of reusable building blocks. This modular approach makes composing and customizing queries based on specific needs easier.&lt;/p&gt;

&lt;p&gt;If you have a fragment that needs to be shared across multiple components, you can have these live in the fragments folder, as stated earlier in the article. The query would look like this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const GET_MOVIES = gql`
  ${MovieFragment}
  ${ActorFragment}
  query GET_MOVIES {
    movies {
      nodes {
        ...MovieFragment
      }
    }
    actors {
      nodes {
        ...ActorFragment
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we would go into the fragments folder and create our fragments there.  Once that is done, we would import them into the component using them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this article provided a better understanding of using fragments with WPGraphQL and Faust.js.&lt;/p&gt;

&lt;p&gt;As always, super stoked to hear your feedback and any questions you might have on headless WordPress. Hit us up on our &lt;a href="https://discord.com/invite/J2khkF9XYK"&gt;Discord&lt;/a&gt; channel!&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>nextjs</category>
      <category>wordpress</category>
      <category>fragments</category>
    </item>
    <item>
      <title>Working with the Apollo Client in Faust.js</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Fri, 24 Mar 2023 14:35:51 +0000</pubDate>
      <link>https://dev.to/franadev/working-with-the-apollo-client-in-faustjs-3ig6</link>
      <guid>https://dev.to/franadev/working-with-the-apollo-client-in-faustjs-3ig6</guid>
      <description>&lt;p&gt;In this article, I will discuss how to work with the  Apollo Client, the new GraphQL client library that is used in the Faust.js framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Faust.js
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://faustjs.org/"&gt;Faust.js is the front-end JavaScript framework&lt;/a&gt; built on top of Next.js, created to make developing headless WordPress sites easier. The idea of this new version is to make a parity between the WordPress world that a developer might be used to but replicate it as much as possible in the JavaScript front-end world. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Apollo Client
&lt;/h2&gt;

&lt;p&gt;Faust uses Apollo for its GraphQL client.  The &lt;a href="https://www.apollographql.com/docs/"&gt;Apollo client&lt;/a&gt; is a data-fetching library for JavaScript that enables you to manage both local and remote data with GraphQL.  It has features you can use for fetching, modifying app data, and caching.  We are going to focus on how to use it in Faust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage in Faust.js
&lt;/h3&gt;

&lt;p&gt;Within Faust, you can use the client in your components and pages.  Take this example here from the &lt;a href="https://faustjs.org/docs/apollo"&gt;Faust docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql, useQuery } from '@apollo/client';

export default function Page(props) {
  const { data } = useQuery(Page.query);
  const { title, description } = data.generalSettings;
  return(
    &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;{description}&amp;lt;/p&amp;gt;
  )
}

Page.query = gql`
  query {
    generalSettings {
      title
      description
    }
  }
`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s go over what is happening in this file.  At the top of the file, &lt;code&gt;graphql&lt;/code&gt; and the &lt;code&gt;useQuery&lt;/code&gt; hook from Apollo is being imported into the file.  Then we have a default function called &lt;code&gt;Page&lt;/code&gt; that will render the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; of the general settings from your WPGraphQL API.  After passing the props into the &lt;code&gt;Page&lt;/code&gt; function, we create a variable that contains the &lt;code&gt;data&lt;/code&gt; object that is the destructured response from the &lt;code&gt;useQuery&lt;/code&gt; hook provided by Apollo. This then takes &lt;code&gt;Page.query&lt;/code&gt; as a parameter.&lt;/p&gt;

&lt;p&gt;At the bottom of the file is our actual GraphQL query which &lt;code&gt;Page.query&lt;/code&gt; is coming from as a parameter. We are calling in the constant variable with the &lt;code&gt;useQuery&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;Back above the return statement, we have an object that contains the destructured fields queried which are the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; from the data in WordPress &lt;code&gt;generalSettings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It co-locates the GraphQL query within the same file. You can also import and use queries from a file.&lt;/p&gt;

&lt;p&gt;The benefit of Faust is that you don’t have to create the client object in your codebase. It is automatically created when you first import the &lt;code&gt;@faustwp-core&lt;/code&gt; package. Instead, you can customize it by using a plugin filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom plugin filter 🔌: Pagination example
&lt;/h2&gt;

&lt;p&gt;For this article, let’s create a custom plugin filter to use cursor-based pagination in Faust.js with Apollo.  If you need a deeper understanding of pagination, please check out this article I wrote on &lt;a href="https://developers.wpengine.com/blog/pagination-in-headless-wordpress-with-wpgraphql-apollo-next-js"&gt;Pagination in Headless WP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is go into the &lt;code&gt;components&lt;/code&gt; folder at the root of the Faust project.  In the components folder, create a file called &lt;code&gt;LoadMorePost.js&lt;/code&gt; and paste this code block in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useQuery, gql } from "@apollo/client";
import Link from "next/link";

const GET_POSTS = gql`
  query getPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          databaseId
          title
          slug
        }
      }
    }
  }
`;

const BATCH_SIZE = 5;

export default function LoadMorePost() {
  const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {
    variables: { first: BATCH_SIZE, after: null },

  });

  console.log(data);

  if (error) {
    return &amp;lt;p&amp;gt;Sorry, an error happened. Reload Please&amp;lt;/p&amp;gt;;
  }

  if (!data &amp;amp;&amp;amp; loading) {
    return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  }

  if (!data?.posts.edges.length) {
    return &amp;lt;p&amp;gt;no posts have been published&amp;lt;/p&amp;gt;;
  }

  const posts = data.posts.edges.map((edge) =&amp;gt; edge.node);
  const haveMorePosts = Boolean(data?.posts?.pageInfo?.hasNextPage);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;ul style={{ padding: "0" }}&amp;gt;
        {posts.map((post) =&amp;gt; {
          const { databaseId, title, slug } = post;
          return (
            &amp;lt;li
              key={databaseId}
              style={{
                border: "2px solid #ededed",
                borderRadius: "10px",
                padding: "2rem",
                listStyle: "none",
                marginBottom: "1rem",
              }}
            &amp;gt;
              &amp;lt;Link href={`${slug}`}&amp;gt;{title}&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
      {haveMorePosts ? (
        &amp;lt;form
          method="post"
          onSubmit={(event) =&amp;gt; {
            event.preventDefault();
            fetchMore({ variables: { after: data.posts.pageInfo.endCursor } });
          }}
        &amp;gt;
          &amp;lt;button type="submit" disabled={loading}&amp;gt;
            {loading ? "Loading..." : "Load more"}
          &amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
      ) : (
        &amp;lt;p&amp;gt;✅ All posts loaded.&amp;lt;/p&amp;gt;
      )}
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this file down into chunks. At the top of the file, I am importing the &lt;code&gt;useQuery&lt;/code&gt; hook and &lt;code&gt;gql&lt;/code&gt; provided by the Apollo client that I am using as well as &lt;code&gt;next/link&lt;/code&gt; from Next.js. We will need these imports in this file.&lt;/p&gt;

&lt;p&gt;The next thing you see is the query we created with cursor-based pagination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const GET_POSTS = gql`
  query getPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          databaseId
          title
          slug
        }
      }
    }
  }
`;

const BATCH_SIZE = 5;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, I have a default components function called &lt;code&gt;LoadMorePost&lt;/code&gt;. In this function, I am utilizing the &lt;code&gt;useQuery&lt;/code&gt; hook in Apollo to pass in my query called &lt;code&gt;GET_POSTS&lt;/code&gt; from the top of the file.&lt;/p&gt;

&lt;p&gt;Next, I have variables that I pass in, which is the &lt;code&gt;batch size&lt;/code&gt; I defined to be &lt;code&gt;5&lt;/code&gt; , and after null which tells the query to start from the beginning of the batch. This function gets fired off each time the user clicks the &lt;code&gt;“load more”&lt;/code&gt; button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function LoadMorePost() {
  const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {
    variables: { first: BATCH_SIZE, after: null },

  });

  console.log(data);

  if (error) {
    return &amp;lt;p&amp;gt;Sorry, an error happened. Reload Please&amp;lt;/p&amp;gt;;
  }

  if (!data &amp;amp;&amp;amp; loading) {
    return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  }

  if (!data?.posts.edges.length) {
    return &amp;lt;p&amp;gt;no posts have been published&amp;lt;/p&amp;gt;;
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 variables that get set next. The first variable is &lt;code&gt;posts&lt;/code&gt; which is taking the data that Apollo gives us back and drilling down into it with the &lt;code&gt;posts&lt;/code&gt; and their nested data. The second variable is &lt;code&gt;haveMorePosts&lt;/code&gt; which checks if we have more posts to load but if there are no more &lt;code&gt;posts&lt;/code&gt; we will have to execute something else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const posts = data.posts.edges.map((edge) =&amp;gt; edge.node);
const haveMorePosts = Boolean(data?.posts?.pageInfo?.hasNextPage);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we can display our posts with a return statement with some data drilling within the levels of nesting that comes from the query.&lt;/p&gt;

&lt;p&gt;Focusing now on the return statement, we have a &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; tag. Within that tag, we are mapping over posts and returning a single post with a &lt;code&gt;databaseId&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, and its &lt;code&gt;slug&lt;/code&gt;. For each of those, we are displaying a list item with a &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; tag. This list item will have a title that has a link to the actual individual blog post’s page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
    &amp;lt;&amp;gt;
      &amp;lt;ul style={{ padding: "0" }}&amp;gt;
        {posts.map((post) =&amp;gt; {
          const { databaseId, title, slug } = post;
          return (
            &amp;lt;li
              key={databaseId}
              style={{
                border: "2px solid #ededed",
                borderRadius: "10px",
                padding: "2rem",
                listStyle: "none",
                marginBottom: "1rem",
              }}
            &amp;gt;
              &amp;lt;Link href={`${slug}`}&amp;gt;{title}&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, we have to add a &lt;code&gt;“load more”&lt;/code&gt; button. This button when clicked will load the next batch of posts from the cursor’s point. In order to do this, we take our &lt;code&gt;haveMorePosts&lt;/code&gt; boolean and if we do have more, we will display a form with a button inside of it. When that button is clicked, we have a &lt;code&gt;onSubmit&lt;/code&gt; handler that calls the &lt;code&gt;fetchMorefunction&lt;/code&gt; in Apollo and passes in the variable called after that grabs the current end cursor, which is the unique ID that represents the last post in the data set to grab the next 5 after that end cursor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  {haveMorePosts ? (
        &amp;lt;form
          method="post"
          onSubmit={(event) =&amp;gt; {
            event.preventDefault();
            fetchMore({ variables: { after: data.posts.pageInfo.endCursor } });
          }}
        &amp;gt;
          &amp;lt;button type="submit" disabled={loading}&amp;gt;
            {loading ? "Loading..." : "Load more"}
          &amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
      ) : (
        &amp;lt;p&amp;gt;✅ All posts loaded.&amp;lt;/p&amp;gt;
      )}
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have created our component in Faust for a paginated page to load posts in batches of 5, the next step is to create a page to test this out.  Navigate to the pages directory in the root of the project and create a file called &lt;code&gt;pagination.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In that file, copy and paste this code block in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Head from "next/head";

import LoadMorePost from "../components/LoadMorePost";

export default function LoadMore() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;Load More&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;

      &amp;lt;main&amp;gt;
        &amp;lt;h1&amp;gt;Load More Example&amp;lt;/h1&amp;gt;
        &amp;lt;LoadMorePost /&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, we are importing the component into this file and exporting it as a default function, returning it to render on the page. &lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Plugin Creation in Faust for the client object
&lt;/h3&gt;

&lt;p&gt;The Apollo client can implement &lt;a href="https://relay.dev/docs/guides/graphql-server-specification/"&gt;relay-style pagination&lt;/a&gt; with the relay spec using merge and read functions, which means all the details of &lt;code&gt;connections&lt;/code&gt;, &lt;code&gt;edges&lt;/code&gt; and &lt;code&gt;pageInfo&lt;/code&gt; can be abstracted away, into a single, reusable helper function. WPGraphQL follows the relay spec as well.&lt;/p&gt;

&lt;p&gt;What we need to do is create a plugin for relay-style pagination in order for Faust to utilize that &lt;a href="https://www.apollographql.com/docs/react/pagination/cursor-based/#relay-style-cursor-pagination"&gt;function from Apollo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In order to &lt;a href="https://faustjs.org/docs/plugin-system/creating-a-plugin"&gt;create a Faust plugin&lt;/a&gt;, we are going to use its &lt;code&gt;apply&lt;/code&gt; method which is a JavaScript class. The &lt;code&gt;apply&lt;/code&gt; method has a parameter called &lt;code&gt;hooks&lt;/code&gt; which is passed from &lt;a href="https://www.npmjs.com/package/@wordpress/hooks"&gt;@wordpress/hooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first step is to go to the root of the project and create a folder called &lt;code&gt;plugins&lt;/code&gt;. In this plugin folder, create a file called &lt;code&gt;RelayStylePagination.js&lt;/code&gt;.  Copy and paste this code block into that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { relayStylePagination } from "@apollo/client/utilities";

export class RelayStylePagination {
  apply(hooks) {
    const { addFilter } = hooks;

    addFilter("apolloClientInMemoryCacheOptions", "faust", (options) =&amp;gt; {
      return {
        ...options,
        typePolicies: {
          ...options.typePolicies,
          RootQuery: {
            ...options.typePolicies.RootQuery,
            fields: {
              posts: relayStylePagination(),
            },
          },
          ContentType: {
            fields: {
              contentNodes: relayStylePagination(),
            },
          },
        },
      };
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top of the file, we import the &lt;code&gt;relayStylePagination&lt;/code&gt; function from Apollo’s utility library.  Following that, we create a &lt;code&gt;class component&lt;/code&gt; which is the basic syntax used in a Faust plugin. &lt;/p&gt;

&lt;p&gt;Next, we have an &lt;code&gt;apply&lt;/code&gt; method with the &lt;code&gt;hooks&lt;/code&gt; parameter which is an object that gives you a function called &lt;code&gt;addFilter&lt;/code&gt;. &lt;a href="https://faustjs.org/docs/plugin-system/filters"&gt;The &lt;code&gt;addFilter&lt;/code&gt; function&lt;/a&gt; allows us to modify the Apollo Client’s &lt;code&gt;InMemoryCacheOptions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the next few lines of code, we are taking the &lt;code&gt;addFilter&lt;/code&gt; hook and calling the &lt;code&gt;memory cache&lt;/code&gt; function &lt;code&gt;options&lt;/code&gt;. The &lt;code&gt;options&lt;/code&gt; are coming from the &lt;a href="https://www.apollographql.com/docs/react/caching/cache-configuration/"&gt;Apollo Client Cache configuration&lt;/a&gt;. These &lt;code&gt;options&lt;/code&gt; allow for configuring the cache’s behavior to suit our use case. In this article, we are defining the configuration for pagination. The &lt;code&gt;faust&lt;/code&gt; namespace follows that.&lt;/p&gt;

&lt;p&gt;Next, we spread through our &lt;code&gt;options&lt;/code&gt; which is a callback function that can return the &lt;code&gt;inMem cache options&lt;/code&gt; as they are. We can also merge in our own with &lt;code&gt;typePolicies&lt;/code&gt; that we define along with other specific queries we need to merge in the future. &lt;a href="https://www.apollographql.com/docs/react/caching/cache-configuration/#typepolicy-fields"&gt;The &lt;code&gt;typePolicies&lt;/code&gt;&lt;/a&gt; is a class that defines a policy of your type in Apollo. Here, we are adding it to the &lt;code&gt;RootQuery&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  addFilter("apolloClientInMemoryCacheOptions", "faust", (options) =&amp;gt; {
      return {
        ...options,
        typePolicies: {
          ...options.typePolicies,
          RootQuery: {
            ...options.typePolicies.RootQuery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last few lines of code are where we are defining our fields of the &lt;code&gt;posts&lt;/code&gt; type. Once those are defined, we can now use the relay spec used by WPGraphQL to tell Apollo and its spec in our pagination method to go ahead and append the previous and next list together using cursor-based pagination by calling the &lt;code&gt;relayStylePagination&lt;/code&gt; function provided. Another thing to note is I also added &lt;code&gt;ContentType&lt;/code&gt; to expose the &lt;code&gt;contentNodes&lt;/code&gt; fields in case of using something like search functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   fields: {
              posts: relayStylePagination(),
            },
          },
          ContentType: {
            fields: {
              contentNodes: relayStylePagination(),
            },
          },
        },
      };
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, we need to go to our &lt;code&gt;faust.config.js&lt;/code&gt; file to imbed it into the &lt;code&gt;experimentalPlugins&lt;/code&gt; key as a value like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { setConfig } from "@faustwp/core";
import templates from "./wp-templates";
import possibleTypes from "./possibleTypes.json";
import { RelayStylePagination } from "./plugins/RelayStylePagination";

/**
 * @type {import('@faustwp/core').FaustConfig}
 **/
export default setConfig({
  templates,
  experimentalPlugins: [new RelayStylePagination()],
  possibleTypes,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generating possible types in Apollo
&lt;/h3&gt;

&lt;p&gt;Before you run the dev server, make sure you type the command &lt;code&gt;npm run generate&lt;/code&gt; since we updated the schema. The Apollo client requires that we provide a &lt;code&gt;possibleTypes&lt;/code&gt; object that maps over our updated schema. The Faust framework provides the command script for you.&lt;/p&gt;

&lt;p&gt;Stoked! Once you have added it to the config file in the plugins, this will allow you to have the pagination you expect when the user clicks your load more button and expects the next batch of posts!!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yXUWt5Fp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ai3095hbt0exuukilk4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yXUWt5Fp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ai3095hbt0exuukilk4g.png" alt="Image description" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Previous and Next Post Link extension of WPGraphQL
&lt;/h3&gt;

&lt;p&gt;The last piece I want to cover is using the WP Template to your advantage to create components and where those GraphQL queries will live and co-locate.&lt;/p&gt;

&lt;p&gt;Out of the box, WPGraphQL does not have the ability to expose and query for &lt;code&gt;previous&lt;/code&gt; and &lt;code&gt;next&lt;/code&gt; post. &lt;a href="https://github.com/kellenmace/pagination-station/blob/main/pagination-fields.php"&gt;The pagination fields&lt;/a&gt; plugin will allow us to modify the GraphQL schema and accomplish this.&lt;/p&gt;

&lt;p&gt;Once this plugin is downloaded into your WP admin, you can now query WPGraphQL for the links. The query I made looks like this which you can run in GraphiQL IDE with the &lt;code&gt;slug&lt;/code&gt; as the query variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; query getPost($slug: ID!) {
    post(id: $slug, idType: SLUG) {
      databaseId
      title
      content
      slug
      previousPost {
        title
        slug
      }
      nextPost {
        title
        slug
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am querying for a single post via its slug as the variable. At the bottom of the query is where I am able to also query for the previous and next posts with the single post query as the starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finalizing Previous and Next posts in Faust.js
&lt;/h3&gt;

&lt;p&gt;Back in my faust.js app, navigate to the components directory at the root of the project. From there, go to the &lt;code&gt;Footer&lt;/code&gt; folder and create a file called &lt;code&gt;PaginatedFooter.js&lt;/code&gt; and copy this block and paste it in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Link from "next/link";

export default function PaginatedFooter({ post }) {
  const { previousPost, nextPost } = post;
  return (
    &amp;lt;&amp;gt;
      &amp;lt;footer style={{ display: "flex", textAlign: "center" }}&amp;gt;
        {previousPost ? (
          &amp;lt;div
            style={{
              border: "2px solid #ddd",
              padding: "1rem",
            }}
          &amp;gt;
            &amp;lt;Link href={`${previousPost.slug}`}&amp;gt;
              &amp;lt;a&amp;gt;👈 {previousPost.title}&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : null}
        {nextPost ? (
          &amp;lt;div
            style={{
              border: "2px solid #ddd",
              padding: "1rem",
              marginLeft: "1rem",
            }}
          &amp;gt;
            &amp;lt;Link href={`${nextPost.slug}`}&amp;gt;
              &amp;lt;a&amp;gt;{nextPost.title} 👉&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : null}
      &amp;lt;/footer&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top of the file, I am importing &lt;code&gt;Link&lt;/code&gt; from &lt;code&gt;next/link&lt;/code&gt; using Next.js’s client-side navigation to link pages.&lt;/p&gt;

&lt;p&gt;Then I have a default function called &lt;code&gt;PaginatedFooter&lt;/code&gt; that is accepting the &lt;code&gt;post&lt;/code&gt; data as its props. The following is a constant variable that destructures the &lt;code&gt;post&lt;/code&gt; props of &lt;code&gt;previousPost&lt;/code&gt; and &lt;code&gt;nextPost&lt;/code&gt; which we now have exposed and are querying from WPGraphQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function PaginatedFooter({ post }) {
  const { previousPost, nextPost } = post;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, I have a return statement wrapped in a fragment that will render a &lt;code&gt;footer&lt;/code&gt; tag. In this &lt;code&gt;footer&lt;/code&gt; tag, I have &lt;code&gt;previousPost&lt;/code&gt;, and if the post does exist, we display that previous &lt;code&gt;post title&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;next/link&lt;/code&gt;, the user has access to a clickable link to route them to that previous post page. Otherwise, if we do not have a previous post, it will render &lt;code&gt;null&lt;/code&gt;, and nothing will appear.&lt;/p&gt;

&lt;p&gt;After that, we have a similar &lt;code&gt;JSX&lt;/code&gt; called &lt;code&gt;nextPost&lt;/code&gt; which does the exact same thing as &lt;code&gt;previousPost&lt;/code&gt; except it will show and render the next post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
    &amp;lt;&amp;gt;
      &amp;lt;footer style={{ display: "flex", textAlign: "center" }}&amp;gt;
        {previousPost ? (
          &amp;lt;div
            style={{
              border: "2px solid #ddd",
              padding: "1rem",
            }}
          &amp;gt;
            &amp;lt;Link href={`${previousPost.slug}`}&amp;gt;
              &amp;lt;a&amp;gt;👈 {previousPost.title}&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : null}
        {nextPost ? (
          &amp;lt;div
            style={{
              border: "2px solid #ddd",
              padding: "1rem",
              marginLeft: "1rem",
            }}
          &amp;gt;
            &amp;lt;Link href={`${nextPost.slug}`}&amp;gt;
              &amp;lt;a&amp;gt;{nextPost.title} 👉&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : null}
      &amp;lt;/footer&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component is now ready to be embedded into a &lt;code&gt;single-page&lt;/code&gt; WP template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faust.js WordPress templates
&lt;/h3&gt;

&lt;p&gt;Faust.js provides a JavaScript version of the &lt;a href="https://wpshout.com/wordpress-template-hierarchy/"&gt;WordPress template hierarchy&lt;/a&gt; and its system. Let’s utilize this system for our paginated footer component with the &lt;code&gt;single.js&lt;/code&gt; file which is the WP template component that renders the single-post details page.&lt;/p&gt;

&lt;p&gt;Navigating to the &lt;code&gt;wp-templates/single.js&lt;/code&gt;, copy and paste over the current boilerplate code already in this file with this block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql } from "@apollo/client";
import PaginatedFooter from "../components/Footer/PaginatedFooter";
import * as MENUS from "../constants/menus";
import { BlogInfoFragment } from "../fragments/GeneralSettings";
import {
  Header,
  Main,
  Container,
  EntryHeader,
  NavigationMenu,
  ContentWrapper,
  FeaturedImage,
  SEO,
} from "../components";

export default function Component(props) {
  // Loading state for previews
  if (props.loading) {
    return &amp;lt;&amp;gt;Loading...&amp;lt;/&amp;gt;;
  }

  const { title: siteTitle, description: siteDescription } =
    props?.data?.generalSettings;
  const primaryMenu = props?.data?.headerMenuItems?.nodes ?? [];
  const footerMenu = props?.data?.footerMenuItems?.nodes ?? [];
  const { title, content, featuredImage, date, author } = props.data.post;

  return (
    &amp;lt;&amp;gt;
      &amp;lt;SEO
        title={siteTitle}
        description={siteDescription}
        imageUrl={featuredImage?.node?.sourceUrl}
      /&amp;gt;
      &amp;lt;Header title={siteTitle} description={siteDescription} /&amp;gt;
      &amp;lt;Main&amp;gt;
        &amp;lt;&amp;gt;
          &amp;lt;EntryHeader
            title={title}
            image={featuredImage?.node}
            date={date}
            author={author?.node?.name}
          /&amp;gt;

          &amp;lt;Container&amp;gt;
            &amp;lt;ContentWrapper content={content} /&amp;gt;
          &amp;lt;/Container&amp;gt;
        &amp;lt;/&amp;gt;
      &amp;lt;/Main&amp;gt;
      &amp;lt;PaginatedFooter post={props.data.post} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  ${FeaturedImage.fragments.entry}
  query GetPost(
    $databaseId: ID!
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
    $asPreview: Boolean = false
  ) {
    post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
      title
      content
      date
      author {
        node {
          name
        }
      }
      previousPost {
        title
        slug
      }
      nextPost {
        title
        slug
      }
      ...FeaturedImageFragment
    }
    generalSettings {
      ...BlogInfoFragment
    }
    headerMenuItems: menuItems(where: { location: $headerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
    footerMenuItems: menuItems(where: { location: $footerLocation }) {
      nodes {
        ...NavigationMenuItemFragment
      }
    }
  }
`;

Component.variables = ({ databaseId }, ctx) =&amp;gt; {
  return {
    databaseId,
    headerLocation: MENUS.PRIMARY_LOCATION,
    footerLocation: MENUS.FOOTER_LOCATION,
    asPreview: ctx?.asPreview,
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 109 lines of code in this example so let’s break this down at a high level and then I shall focus on the changes I made to the actual boilerplate starter code with my own customization.&lt;/p&gt;

&lt;p&gt;The templates in Faust as you see in the code block have 3 main parts: Component, Query, and Variable.  Please refer to the &lt;a href="https://faustjs.org/docs/templates#template-structure"&gt;Faust docs&lt;/a&gt; to get a deeper explanation of what each does.&lt;/p&gt;

&lt;p&gt;Let’s start by focusing on the query layer of the template.  At the bottom of the file, I have customized the query to ask for the &lt;code&gt;previousPost&lt;/code&gt; and &lt;code&gt;nextPost&lt;/code&gt; fields as shown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Component.query = gql`
  ${BlogInfoFragment}
  ${NavigationMenu.fragments.entry}
  ${FeaturedImage.fragments.entry}
  query GetPost(
    $databaseId: ID!
    $headerLocation: MenuLocationEnum
    $footerLocation: MenuLocationEnum
    $asPreview: Boolean = false
  ) {
    post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
      title
      content
      date
      author {
        node {
          name
        }
      }
      previousPost {
        title
        slug
      }
      nextPost {
        title
        slug
      }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variables are already set to what I need, so the last thing I have to do is go back up to the top of the component which is the rendering layer and add the &lt;code&gt;PaginatedFooter&lt;/code&gt; component after importing it at the top within the return statement.  Then I can pass in and destructure the &lt;code&gt;post props&lt;/code&gt; data in the &lt;code&gt;PaginatedFooter&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
    &amp;lt;&amp;gt;
      &amp;lt;SEO
        title={siteTitle}
        description={siteDescription}
        imageUrl={featuredImage?.node?.sourceUrl}
      /&amp;gt;
      &amp;lt;Header title={siteTitle} description={siteDescription} /&amp;gt;
      &amp;lt;Main&amp;gt;
        &amp;lt;&amp;gt;
          &amp;lt;EntryHeader
            title={title}
            image={featuredImage?.node}
            date={date}
            author={author?.node?.name}
          /&amp;gt;

          &amp;lt;Container&amp;gt;
            &amp;lt;ContentWrapper content={content} /&amp;gt;
          &amp;lt;/Container&amp;gt;
        &amp;lt;/&amp;gt;
      &amp;lt;/Main&amp;gt;
      &amp;lt;PaginatedFooter post={props.data.post} /&amp;gt;
    &amp;lt;/&amp;gt;
  );


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Super Stoked! First, run &lt;code&gt;npm run generate&lt;/code&gt; for the types and then run this on the dev server. Let’s see this now work on the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hlFdcTzO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v12kmyzk4u1ki61ibn5u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hlFdcTzO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v12kmyzk4u1ki61ibn5u.png" alt="Image description" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 🚀
&lt;/h2&gt;

&lt;p&gt;Faust.js and its new GraphQL client Apollo give developers a better way to develop headless WordPress.&lt;/p&gt;

&lt;p&gt;I hope you have a better understanding of how to work with Apollo in Faust.js. As always, super stoked to hear your feedback and any questions you might have on headless WordPress. Hit us up in our &lt;a href="https://discord.com/invite/J2khkF9XYK"&gt;discord&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>apollo</category>
      <category>wordpress</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Pagination in Headless WordPress with WPGraphQL, Apollo, &amp; Next.js</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Tue, 07 Feb 2023 16:26:19 +0000</pubDate>
      <link>https://dev.to/franadev/pagination-in-headless-wordpress-with-wpgraphql-apollo-nextjs-3n3e</link>
      <guid>https://dev.to/franadev/pagination-in-headless-wordpress-with-wpgraphql-apollo-nextjs-3n3e</guid>
      <description>&lt;p&gt;In this article, I will discuss how WPGraphQL does cursor-based pagination in headless WordPress along with using Next.js and Apollo GraphQL for my client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination 📃
&lt;/h2&gt;

&lt;p&gt;Pagination on the web is defined as a process of breaking or separating the content of a website into different pages. The user can use links such as “next,” “previous” and page numbers to navigate between pages and display sets of data when there is too much data to display all of the records at one time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cursor-based Pagination in WPGraphQL
&lt;/h3&gt;

&lt;p&gt;Out of the box, WPGraphQL ships with what is called &lt;a href="https://www.wpgraphql.com/2020/03/26/forward-and-backward-pagination-with-wpgraphql" rel="noopener noreferrer"&gt;cursor-based&lt;/a&gt; pagination. This method loads the initial set of records and provides a cursor which is the reference point for the next request to utilize it and make the request for the next batch of records.&lt;/p&gt;

&lt;p&gt;Traditional WordPress uses what is called offset-based pagination. This method groups post within the page numbers, essentially putting parameters within the client requests with a specific limit or number of results and then offset, the number of records that need to be skipped.&lt;/p&gt;

&lt;p&gt;I won’t go too deep into the pros and cons of each in this article. Please check out Jason Bahl’s &lt;a href="https://www.wpgraphql.com/2020/03/26/forward-and-backward-pagination-with-wpgraphql" rel="noopener noreferrer"&gt;article&lt;/a&gt; here if you want to get a deeper dive into each method.&lt;/p&gt;

&lt;h2&gt;
  
  
  WPGraphQL Queries with Cursor-based pagination 🐘
&lt;/h2&gt;

&lt;p&gt;Let’s navigate to the WP Admin and GraphiQL IDE now to discuss how to query WPGraphQL with cursor-based pagination. Below is a screenshot of my initial query with the fields and arguments that come out of the box for pagination with WPGraphQL: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3sbwyxkp2u2hrs6xlmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3sbwyxkp2u2hrs6xlmz.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this initial query, I am asking for a list of my posts, with the arguments of grabbing the first 3 posts and then after that "null" or starting from the beginning of the list.    &lt;/p&gt;

&lt;p&gt;What if instead of starting at the beginning of the list, I want to request the list of posts after the first 3?  This is where the cursor field and &lt;code&gt;pageInfo&lt;/code&gt; type come in.  &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pageInfo&lt;/code&gt; type has 2 fields called &lt;code&gt;hasNextPage&lt;/code&gt; and &lt;code&gt;endCursor&lt;/code&gt;.  The &lt;code&gt;hasNextPage&lt;/code&gt; field allows you to find out if you have more pages with your posts on that data set.  The &lt;code&gt;endCursor&lt;/code&gt; field is a unique identifier string that represents the last post of the data set you are requesting.  It points to that last post on the request as shown here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rkcw986rwsa47ebiacs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rkcw986rwsa47ebiacs.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essentially, I can now ask for every post before or after that unique identifier string that &lt;code&gt;endCursor&lt;/code&gt; gives me instead of starting from the beginning. In this case, the post tied to that unique ID is "&lt;br&gt;
Obi-Wan". When I grab the unique ID string and use it with the &lt;code&gt;after&lt;/code&gt; argument instead of &lt;code&gt;null&lt;/code&gt;, the query will start from that post and give me all the posts after it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r3ilej2xhedjdro8tfb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r3ilej2xhedjdro8tfb.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This opens up a realm of other possibilities. You can just swap out the end cursor and fire off queries to get the next subset of results after the last one from that cursor. You can do it bi-directionally as well where you can get the last 3 before the end cursor, paginating both forward and backward.&lt;/p&gt;

&lt;p&gt;There are performance gains in this method.  Because it uses the unique ID to locate the record and then counts forward or backward from that ID instead of loading every dataset in the case of offset pagination, it requires fewer resources in loading batches of data.&lt;/p&gt;

&lt;p&gt;Let's re-write our query so that we can dynamically pass in the argument in the fields instead of hard coding the string, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query getPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          databaseId
          title
          slug
        }
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are accepting input arguments &lt;code&gt;first&lt;/code&gt; which is an integer and &lt;code&gt;after&lt;/code&gt; which is a string.  In our front-end app, we can pass in the first 3 then when the user hits the load more button, our app then grabs the end cursor and will pass in different query variables to get the next set of data results on whichever end cursor string was tied to that post.&lt;/p&gt;

&lt;p&gt;This query is now ready to use on our first pagination example called Load More which will be used in our Next.js front-end using the Apollo client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load-More in Next.js and Apollo
&lt;/h2&gt;

&lt;p&gt;In my Next.js application, I have a file called &lt;code&gt;LoadMorePost.js&lt;/code&gt; which is a component that lives in my component folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useQuery, gql } from "@apollo/client";
import Link from "next/link";

const GET_POSTS = gql`
  query getPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          databaseId
          title
          slug
        }
      }
    }
  }
`;

const BATCH_SIZE = 5;

export default function LoadMorePost() {
  const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {
    variables: { first: BATCH_SIZE, after: null },
    notifyOnNetworkStatusChange: true,
  });

  if (error) {
    return &amp;lt;p&amp;gt;Sorry, an error happened. Reload Please&amp;lt;/p&amp;gt;;
  }

  if (!data &amp;amp;&amp;amp; loading) {
    return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  }

  if (!data?.posts.edges.length) {
    return &amp;lt;p&amp;gt;no posts have been published&amp;lt;/p&amp;gt;;
  }

  const posts = data.posts.edges.map((edge) =&amp;gt; edge.node);
  const haveMorePosts = Boolean(data?.posts?.pageInfo?.hasNextPage);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;ul style={{ padding: "0" }}&amp;gt;
        {posts.map((post) =&amp;gt; {
          const { databaseId, title, slug } = post;
          return (
            &amp;lt;li
              key={databaseId}
              style={{
                border: "2px solid #ededed",
                borderRadius: "10px",
                padding: "2rem",
                listStyle: "none",
                marginBottom: "1rem",
              }}
            &amp;gt;
              &amp;lt;Link href={`/blog/${slug}`}&amp;gt;{title}&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
      {haveMorePosts ? (
        &amp;lt;form
          method="post"
          onSubmit={(event) =&amp;gt; {
            event.preventDefault();
            fetchMore({ variables: { after: data.posts.pageInfo.endCursor } });
          }}
        &amp;gt;
          &amp;lt;button type="submit" disabled={loading}&amp;gt;
            {loading ? "Loading..." : "Load more"}
          &amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
      ) : (
        &amp;lt;p&amp;gt;✅ All posts loaded.&amp;lt;/p&amp;gt;
      )}
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this file down into chunks. At the top of the file, I am importing the &lt;code&gt;useQuery&lt;/code&gt; hook and &lt;code&gt;gql&lt;/code&gt; provided by the Apollo client that I am using as well as &lt;code&gt;next/link&lt;/code&gt; from Next.js. We will need these imports in this file.&lt;/p&gt;

&lt;p&gt;The next thing you see is the query we created in GraphiQL back in our WordPress admin with the assistance of WPGraphQL which will allow us to fire off requests to WPGraphQL and use cursor-based pagination.&lt;/p&gt;

&lt;p&gt;The following line shows the number of posts I want to grab in a constant in &lt;code&gt;BATCH_SIZE&lt;/code&gt;. When the user hits the load more button, it will populate 5 posts in each load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const GET_POSTS = gql`
  query getPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          databaseId
          title
          slug
        }
      }
    }
  }
`;

const BATCH_SIZE = 5;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, I have a default components function called &lt;code&gt;LoadMorePost&lt;/code&gt;. In this function, I am making use of the &lt;code&gt;useQuery&lt;/code&gt; hook in Apollo to pass in my query called &lt;code&gt;GET_POSTS&lt;/code&gt; from the top of the file. Next, I have variables that I pass in, which was the batch size I defined to be 5 and after &lt;code&gt;null&lt;/code&gt; or start from the beginning. This function gets fired off each time the user clicks the “load more” button.&lt;/p&gt;

&lt;p&gt;Following that, I have some &lt;code&gt;if&lt;/code&gt; conditionals that invoke execution of possible states if an “error,” “loading,” or if we have no posts and the request has finished then we have no more posts published. If those checks have all passed, it means we have posts to be displayed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function LoadMorePost() {
  const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {
    variables: { first: BATCH_SIZE, after: null },
    notifyOnNetworkStatusChange: true,
  });

  if (error) {
    return &amp;lt;p&amp;gt;Sorry, an error happened. Reload Please&amp;lt;/p&amp;gt;;
  }

  if (!data &amp;amp;&amp;amp; loading) {
    return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  }

  if (!data?.posts.edges.length) {
    return &amp;lt;p&amp;gt;no posts have been published&amp;lt;/p&amp;gt;;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 variables that get set next. The first variable is &lt;code&gt;posts&lt;/code&gt; which is taking the data that Apollo gives us back and drilling down into it with the posts and their nested data. The second variable is &lt;code&gt;haveMorePosts&lt;/code&gt; which checks if we have more posts to load but if there are no more posts we will have to execute something else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const posts = data.posts.edges.map((edge) =&amp;gt; edge.node);
  const haveMorePosts = Boolean(data?.posts?.pageInfo?.hasNextPage);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we can display our posts with a &lt;code&gt;return&lt;/code&gt; statement with some data drilling within the levels of nesting that comes from the query.&lt;/p&gt;

&lt;p&gt;Focusing now on the &lt;code&gt;return&lt;/code&gt; statement, we have a &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; tag. Within that tag, we are mapping over posts and returning a single post with a &lt;code&gt;databaseId&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, and its &lt;code&gt;slug&lt;/code&gt;. For each of those, we are displaying a list item with a &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;tag. This list item will have a title that has a link to the actual individual blog post’s page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;ul style={{ padding: "0" }}&amp;gt;
        {posts.map((post) =&amp;gt; {
          const { databaseId, title, slug } = post;
          return (
            &amp;lt;li
              key={databaseId}
              style={{
                border: "2px solid #ededed",
                borderRadius: "10px",
                padding: "2rem",
                listStyle: "none",
                marginBottom: "1rem",
              }}
            &amp;gt;
              &amp;lt;Link href={`/blog/${slug}`}&amp;gt;{title}&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, we have to add a “load more” button. This button when clicked will load the next batch of posts from the cursor’s point. In order to do this, we take our &lt;code&gt;haveMorePosts&lt;/code&gt; boolean and if we do have more, we will display a form with a button inside of it. When that button is clicked, we have a &lt;code&gt;onSubmit&lt;/code&gt; handler that calls the &lt;code&gt;fetchMore&lt;/code&gt; function in Apollo and passes in the variable called &lt;code&gt;after&lt;/code&gt; that grabs the current end cursor, which is the unique ID that represents the last post in the data set to grab the next 5 after that end cursor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; {haveMorePosts ? (
        &amp;lt;form
          method="post"
          onSubmit={(event) =&amp;gt; {
            event.preventDefault();
            fetchMore({ variables: { after: data.posts.pageInfo.endCursor } });
          }}
        &amp;gt;
          &amp;lt;button type="submit" disabled={loading}&amp;gt;
            {loading ? "Loading..." : "Load more"}
          &amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
      ) : (
        &amp;lt;p&amp;gt;✅ All posts loaded.&amp;lt;/p&amp;gt;
      )}
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is done and I have placed this component in &lt;code&gt;pages/load-more.js&lt;/code&gt; within my Next.js app to give it a route and a page.&lt;/p&gt;

&lt;p&gt;Let’s see how this all looks in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs8wrf8jhtbuk8z9lngm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs8wrf8jhtbuk8z9lngm.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 🚀
&lt;/h2&gt;

&lt;p&gt;Pagination is an important part and very common in modern websites and apps.  In this article, I hope you took away a better understanding of how to paginate in headless WordPress with the best-in-class frameworks and tools: WPGraphQL, Next.js, and the Apollo Client.&lt;/p&gt;

&lt;p&gt;As always, stoked to hear your feedback and any questions you might have on headless WordPress! Hit us up in our discord!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>website</category>
      <category>bug</category>
    </item>
    <item>
      <title>SEO in Headless WordPress with Yoast, Next.js and WPGraphQL</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Wed, 02 Nov 2022 22:29:22 +0000</pubDate>
      <link>https://dev.to/franadev/seo-in-headless-wordpress-with-yoast-nextjs-and-wpgraphql-126e</link>
      <guid>https://dev.to/franadev/seo-in-headless-wordpress-with-yoast-nextjs-and-wpgraphql-126e</guid>
      <description>&lt;p&gt;In this article, I will give a walk-through tutorial on how to manage Search Engine Optimization (SEO) in a headless WordPress architecture.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A fundamental understanding of Next.js and the Node ecosystem&lt;/li&gt;
&lt;li&gt;Foundational SEO best practices knowledge&lt;/li&gt;
&lt;li&gt;A WordPress install&lt;/li&gt;
&lt;li&gt;A basic understanding of the WPGraphQL API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Yoast SEO
&lt;/h2&gt;

&lt;p&gt;Before getting into the Yoast SEO plugin, let's quickly go over what SEO is and its importance. SEO is the practice of orienting your website to rank higher on a search engine results page (SERP) so that you receive more traffic. The aim is typically to rank on the first page of Google results for search terms that mean the most to your target audience.&lt;/p&gt;

&lt;p&gt;Yoast SEO is a WordPress plugin that improves your website’s rankings on search engines by helping you optimize your site’s content and keywords. A lot of what Yoast SEO does is automated such as analyzing a page's content and providing suggestions on how to improve it. The plugin gives you a score, tells you what problems there are and how to improve your content for SEO. But there are things that still need your input such as key phrases and meta descriptions.  &lt;/p&gt;

&lt;p&gt;In this next section, let's go over installing it and extending it with WPGraphQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Yoast SEO and WPGraphQL extensions
&lt;/h3&gt;

&lt;p&gt;To start, we need to set up and configure Yoast and WPGraphQL including its Yoast extension to expose the SEO fields in the GraphQL schema with Yoast.&lt;/p&gt;

&lt;p&gt;Login to your WP Admin. From &lt;code&gt;Plugins &amp;gt; Add new&lt;/code&gt; menu, search for &lt;a href="https://wordpress.org/plugins/wordpress-seo/" rel="noopener noreferrer"&gt;Yoast SEO&lt;/a&gt;, install it, and activate the plugin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe81jiksf70hvsgd3u843.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe81jiksf70hvsgd3u843.png" alt="Image description" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, search for WPGraphQL and Yoast for WPGraphQL, install and activate both:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ldbcembul2vvijkrj53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ldbcembul2vvijkrj53.png" alt="Image description" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stoked!  These are all the plugins we need on the WordPress install to manage SEO in a headless setup!  Let's make sure everything is working as it should be.&lt;/p&gt;

&lt;p&gt;Head over to Posts and click on an individual post to edit it.  When you are in the edit interface of the individual post, you should see an option to select Yoast SEO at the bottom of the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpl8gqfkca948vx6jq3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpl8gqfkca948vx6jq3y.png" alt="Image description" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on that Yoast SEO option and it will reveal tools such as adding a meta description, canonical URL, breadcrumbs title, etc.  There are lots of features to increase and boost your SEO.  For this quick tutorial, we will focus on exposing the meta and full head of the HTML in the post in order for web crawlers to better find and index the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz5xf86n05mtglcn9lfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz5xf86n05mtglcn9lfs.png" alt="Image description" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  WPGraphQL and the SEO field
&lt;/h3&gt;

&lt;p&gt;The WPGraphQL for Yoast extension makes it really easy and seamless to expose and query for the SEO data in the WordPress WPGraphQL schema.  A SEO field is exposed in the GraphiQL IDE and you can ask specifically for what SEO data you want back.  &lt;/p&gt;

&lt;p&gt;On the side menu in your WP admin, go to the &lt;code&gt;GraphiQL&lt;/code&gt; option on the left or the icon at the top of the navbar.  In this case, I am querying for a single post by its slug as the variable and asking for the meta description, title, and the full head of the HTML page in the SEO field. &lt;/p&gt;

&lt;p&gt;This is what my query looks like in a code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query PostBySlug($slug: String!) {
        generalSettings {
          title
        }
        postBy(slug: $slug) {
          id
          content
          title
          slug
          seo {
            metaDesc
            title
            fullHead
          }
        }
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the query looks like in the GraphiQL IDE in WP Admin after you press play to run it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F945tgp1h9483qq5inpuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F945tgp1h9483qq5inpuy.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that it exposes the SEO data via GraphQL schema!! We get back some SEO data goodness!  The next step is to get this data and consume it on our frontend UI with Next.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js Head and Metadata
&lt;/h2&gt;

&lt;p&gt;We have our WordPress install transformed into a WPGraphQL server with the SEO extension to expose that metadata.  The next step is to use Next.js as our frontend app to consume that data and turn it into &lt;code&gt;HTML&lt;/code&gt; and improve the way the search engine indexes our site.&lt;/p&gt;

&lt;p&gt;In this tutorial, I am going to use my Next.js demo &lt;a href="https://github.com/Fran-A-Dev/buddy-run-" rel="noopener noreferrer"&gt;starter&lt;/a&gt;.  Once you clone down the starter, navigate to the root of the project into the pages/[slug].js file.  Go down to the &lt;code&gt;getStaticProps&lt;/code&gt; function that contains our &lt;code&gt;GraphQL&lt;/code&gt; query.  Copy and paste this block over the existing code.  It should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticProps({ params }) {
  const GET_POST = gql`
    query PostBySlug($id: ID!) {
      post(id: $id, idType: SLUG) {
        title
        content
        date
        seo {
          metaDesc
          fullHead
          title
        }
        author {
          node {
            firstName
            lastName
          }
        }
      }
    }
  `;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file and query are grabbing an individual post with its related data.  The SEO meta description, full head, and title are also being requested! Stoked!!&lt;/p&gt;

&lt;p&gt;Now that our SEO data is being requested and coming through, let's show it on our site by adding it to our variable in Next.js and destructure it so we can add it to our &lt;code&gt;JSX&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;At the top of the &lt;code&gt;[slug].js&lt;/code&gt; file, copy this code block and paste it over the existing code.  It should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { client } from "../lib/apollo";
import { gql } from "@apollo/client";

import Head from "next/head";

export default function SlugPage({ post }) {
  const { seo } = post;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;{seo.title}&amp;lt;/title&amp;gt;
        &amp;lt;meta name="description" content={seo.metaDesc} /&amp;gt;
        &amp;lt;link rel="icon" href="/favicon.ico" /&amp;gt;
      &amp;lt;/Head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run this locally in terminal to pull up the site on the browser with &lt;code&gt;npm run dev&lt;/code&gt; and open up the dev tools to inspect the elements in the &lt;code&gt;Head&lt;/code&gt; tag, you should see all the SEO data that you requested:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frej9n2ps41iiyc0wi27j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frej9n2ps41iiyc0wi27j.png" alt="Image description" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are now optimized for SEO using &lt;a href="https://nextjs.org/docs/api-reference/next/head" rel="noopener noreferrer"&gt;next/head&lt;/a&gt; which is a built-in component in Next.js that allows us to append elements at the head of each page.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Full Head and HTML Parser
&lt;/h2&gt;

&lt;p&gt;Managing the entire Head of your page is made easier with the full head field within the WPGraphQL schema.  The issue here is the &lt;code&gt;next/head&lt;/code&gt; component does not support React &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; convention.&lt;/p&gt;

&lt;p&gt;In order to alleviate this issue we can use the html-react-parser package.  Go to terminal and install it like so in your project directory:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5eeiicsqu8xv0cv9in5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5eeiicsqu8xv0cv9in5.png" alt="Image description" width="800" height="31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once installed, go back to the [slug].js file and copy this code block from top to bottom and your full file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { client } from "../lib/apollo";
import { gql } from "@apollo/client";
import parse from "html-react-parser";
import Head from "next/head";

export default function SlugPage({ post }) {
  const fullHead = parse(post.seo.fullHead);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Head&amp;gt;{fullHead}&amp;lt;/Head&amp;gt;

      &amp;lt;main&amp;gt;
        &amp;lt;div className="siteHeader"&amp;gt;
          &amp;lt;h1 className="title"&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
          &amp;lt;p&amp;gt;
            ✍️ &amp;amp;nbsp;&amp;amp;nbsp;
            {`${post.author.node.firstName} ${post.author.node.lastName}`} | 🗓️
            &amp;amp;nbsp;&amp;amp;nbsp;{new Date(post.date).toLocaleDateString()}
          &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;article dangerouslySetInnerHTML={{ __html: post.content }}&amp;gt;&amp;lt;/article&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export async function getStaticProps({ params }) {
  const GET_POST = gql`
    query PostBySlug($id: ID!) {
      post(id: $id, idType: SLUG) {
        title
        content
        date
        seo {
          metaDesc
          fullHead
          title
        }
        author {
          node {
            firstName
            lastName
          }
        }
      }
    }
  `;
  //  the params argument for this function corresponds to the dynamic URL segments
  //  we included in our page-based route. So, in this case, the `params` object will have
  //  a property named `uri` that contains that route segment when a user hits the page
  const response = await client.query({
    query: GET_POST,
    variables: {
      id: params.slug,
    },
  });
  const post = response?.data?.post;

  return {
    props: {
      post,
    },
  };
}

export async function getStaticPaths() {
  const paths = [];
  return {
    paths,
    fallback: "blocking",
  };
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top of the file, we import the parse from &lt;code&gt;html-react-parser&lt;/code&gt;.  Then, we set a const up to call the full head and transform it into react.  Once that is done, we simply add the &lt;code&gt;jsx&lt;/code&gt; within the head tag grabbing the full head data in one object.  &lt;/p&gt;

&lt;p&gt;Going back to terminal and running it locally, we can see that it is working!!!  The entire full head is showing with all the metadata and canonical URLs!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nxsoxp61o4oxxtf675d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nxsoxp61o4oxxtf675d.png" alt="Image description" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy On Atlas
&lt;/h2&gt;

&lt;p&gt;This works locally on our machine.  Let's get it deployed and live on the internet and check to see if we get the same results.&lt;/p&gt;

&lt;p&gt;I will use Atlas, WP Engine's Headless WordPress hosting platform, specifically built to optimize for headless.  I already have an account all set up with my GitHub repo accessible on Atlas.  All I need to do is push this change and trigger that build and deployment.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1odurgcinpxfhfyyjqc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1odurgcinpxfhfyyjqc6.png" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once my app is deployed on Atlas, the unique URL is updated.  Hit this &lt;a href="https://h5sfgvn7s4n202u59mizuzws1.js.wpenginepowered.com/" rel="noopener noreferrer"&gt;URL&lt;/a&gt;, go to any of the post links on the home page and open the dev tools on its detail page.  Once there, inspect the Head elements.  You will see what we saw locally but now it is live on the internet!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4n0lmjmnc3tt0aqirne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4n0lmjmnc3tt0aqirne.png" alt="Image description" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SEO is an important part of your site's discoverability when it comes to Google and other search engines indexing your site and ranking them high.  It makes your website more visible, and that means more traffic.&lt;/p&gt;

&lt;p&gt;With the tools like WPGraphQL, Yoast SEO, Next.js and Atlas to host, this is made much more easier and seamless for the developer.&lt;/p&gt;

&lt;p&gt;This tutorial covered just the basics of what Yoast SEO can do with WPGraphQL.  There are a lot more features and options you can do and I would love to hear about them in our&lt;a href="https://discord.com/invite/J2khkF9XYK" rel="noopener noreferrer"&gt; Discord&lt;/a&gt; channel! &lt;/p&gt;

&lt;p&gt;If you have not used the Atlas platform and are interested, please check this &lt;a href="https://wpengine.com/atlas/#atlasplans" rel="noopener noreferrer"&gt;link&lt;/a&gt; out to get a free sandbox dev account!  &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>seo</category>
      <category>wordpress</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>Planning for Headless WordPress and Atlas</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Wed, 19 Oct 2022 16:59:20 +0000</pubDate>
      <link>https://dev.to/franadev/planning-for-headless-wordpress-and-atlas-5f8k</link>
      <guid>https://dev.to/franadev/planning-for-headless-wordpress-and-atlas-5f8k</guid>
      <description>&lt;p&gt;In this article, I will discuss the topics and concepts that you will need to understand when considering and planning for Atlas as your Headless WordPress platform. Additionally, as you think about workflows, CI/CD, developer team structure, and developing on Atlas as a whole, I will go over several strategies and considerations that can help guide you.&lt;/p&gt;

&lt;p&gt;If you have not yet tested or developed on Atlas, please access a free developer sandbox account &lt;a href="https://wpengine.com/atlas/#atlasplans"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are new to Headless WordPress or Atlas, please reference these guides: &lt;a href="https://wpengine.com/atlas/"&gt;Atlas&lt;/a&gt;, &lt;a href="https://developers.wpengine.com/roadmap"&gt;Headless WordPress Roadmap&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a website in Headless WordPress
&lt;/h2&gt;

&lt;p&gt;The approach of building a Headless WordPress website is different from traditional WordPress in that Headless does not require that the requested data be created and then sent to the server to be processed. Rather, the data is pre-compiled and is made available to the browser through the Content Delivery Network, or CDN for short.&lt;/p&gt;

&lt;p&gt;This rendering begins with your modern JavaScript framework of choice pre-building your files (JavaScript, HTML, etc). It is important that the framework chosen supports static site generation, regardless of which &lt;a href="https://developers.wpengine.com/blog/rendering-patterns-in-headless-wordpress"&gt;rendering pattern&lt;/a&gt; you use. Once the files are generated, you can deploy them on the CDN. &lt;/p&gt;

&lt;p&gt;What this does is it removes the need for the browser to go through a long and time-consuming series of interactions between the server, database, and cache, improving the performance and speed of the website.&lt;/p&gt;

&lt;p&gt;In a traditional monolithic workflow, you have a developer writing code and programming the site. Then they ship it to the server where the server does the work of producing the HTML requested by the client and shipping that to the client. This is done upon every client-side request.&lt;/p&gt;

&lt;p&gt;One key decision in the planning of a Headless WordPress site is which files the developer chooses to generate at runtime versus generating them at build time. The decision to generate your files at build time or server-side rendering them at runtime may depend on several factors – how much content can be delivered statically, requirements for dynamic content (dynamic pages or smaller dynamic components, and performance needs).&lt;/p&gt;

&lt;p&gt;There is the flexibility of using different rendering patterns in Headless WordPress with your choice of a JavaScript framework, however, the foundation of the Jamstack or decoupled methodology is to pre-render as much content as possible during build time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow Steps in using Atlas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Choose a front-end JS framework like Faust.js, Next.js, Vue, etc. Oftentimes this may be based on what you and your team are familiar with. Also consider some of the features of WordPress that the Publishers may require (post previews, sitemaps) and ensure the framework supports these.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose your WordPress API whether that is WP REST or WPGraphQL. Both can be used and if you need help deciding, please reference this &lt;a href="https://www.wpgraphql.com/docs/wpgraphql-vs-wp-rest-api/"&gt;article&lt;/a&gt;. If you have a preexisting WP install and want to migrate the data you can easily do so with the migration plugin via WP Engine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure your front-end code is in GitHub and is integrated with Atlas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create any Markdown files necessary on your front end and render the data to the files. This is outside any dynamic content to be rendered at runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, push changes in GitHub and deploy to Atlas.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Atlas Abstract: What it provides
&lt;/h2&gt;

&lt;p&gt;When you adopt Headless WordPress and choose Atlas, you not only get the Atlas platform, an ecosystem of defined tooling such as a CDN and infrastructure, but Atlas also supplies a lot of moving parts in the abstract. These layers that are abstracted allow developers to have a seamless workflow and experience without having to use multiple vendors, systems, etc. Let’s get into details on what Atlas provides and things you should consider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Integration/Continous Deployment
&lt;/h3&gt;

&lt;p&gt;One of the most difficult things you can do as a developer is to set up a build system and CI/CD pipeline. There is a lot of toil in this workflow that most developers do not want to do. This is why the emergence of services-hosted platforms, such as Atlas, has taken their place in the market. Fully managed, taking care of this all for you.&lt;/p&gt;

&lt;p&gt;The Atlas platform has a simple one-click connection to your Github making it painless to integrate with your repository.&lt;/p&gt;

&lt;p&gt;Other services (such as AWS) may allow you to select your own deployment zone, however, with Atlas, the CDN and the target are baked in. You are provided with everything you need to build and host your Headless site.&lt;/p&gt;

&lt;p&gt;Atlas’s build environment gives you a secure and customizable build step.  When you build your app on Atlas, it will set up your environment and server, grab any needed dependencies, run the build and deploy.  It supports all tooling, front-end frameworks, site generators, etc involved in building your site.  You can customize your node and yarn versions, install dependencies, create custom build and start commands as well as trigger a build with webhooks from the WordPress backend should your content editors need it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atlas CDN
&lt;/h3&gt;

&lt;p&gt;A content delivery network (CDN) is a group of servers that are distributed geographically and work together to provide fast and speedy delivery of internet content. Your assets such as your HTML pages, JavaScript files, stylesheets, images, and videos are quickly transferred onto a CDN for optimal access and performance to your users.&lt;/p&gt;

&lt;p&gt;A properly configured CDN may also help protect websites against some common malicious attacks, such as Distributed Denial of Service (DDOS) attacks.&lt;/p&gt;

&lt;p&gt;A CDN can’t replace the need for proper web hosting, but it does help cache content at the network edge, which improves website performance. Adding it to your website hosting helps optimize for performance.&lt;/p&gt;

&lt;p&gt;The CDN is a layer in your web tech stack that you must account for when you are in the planning phases of adopting Headless WordPress. Doing this ad-hoc and DIY can be time-consuming and tedious for the developer team.  In order to alleviate this issue, Atlas does this for you out of the box.&lt;/p&gt;

&lt;p&gt;Atlas’s out-of-the-box CDN utilizes caching to reduce hosting bandwidth, helping to prevent interruptions in service, and improving security, and performance. A CDN, by nature, is distributed geographically which reduces the time it takes to get to your users. When you combine this with the approach of the Jamstack/decoupled architecture which prebuilds the markup, all those files become available statically on the CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In summary, the Atlas CDN provides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improving website load times&lt;/li&gt;
&lt;li&gt;Reduced bandwidth costs&lt;/li&gt;
&lt;li&gt;Increased content availability and redundancy&lt;/li&gt;
&lt;li&gt;Improved security&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WordPress APIs and 3rd Party APIs
&lt;/h2&gt;

&lt;p&gt;The next thing to consider in planning for the development of Headless WordPress is the WordPress install itself. Questions arise such as where to host it, which API you will use (WPGraphQL or REST), plugins, pairing environments with the JS frontend, and any customization to it.&lt;/p&gt;

&lt;p&gt;Even though a static Jamstack site can indeed be nothing more than a collection of static assets, connecting your application to your WP backend with the WPGraphQL or REST API is what shows the true power and promise of this as an architecture for real-world applications.&lt;/p&gt;

&lt;p&gt;This is the power and foundation behind the modular, decoupled, Jamstack approach. It gives frontend teams the power to not have to write any backend code. They can consume the WordPress data through an API endpoint, mainly REST or GraphQL.&lt;/p&gt;

&lt;p&gt;In fact, you’ll find many traditional players like Shopify, SalesForce, BigCommerce, GitHub, etc now offer APIs so they, too, can be consumed in a headless fashion. The explosion of APIs speaks to the momentum of the decoupled movement as the architecture of the interconnected web. As this API-driven development is becoming the norm, Atlas will support it.&lt;/p&gt;

&lt;p&gt;Once you decide what API you will use for WordPress, you will need a WordPress host for that install. On Atlas, you have a trusted WP hosting platform fully managed.  You can rest assured that your WP install is kept secure, up to date, and given best-in-class maximum uptime as well as provisioning new WP installs with a simple click.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs/Best Practices 🌐 🤓
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Q: Can the size of a developer team affect the decisions on the kind of workflow implemented in Headless WordPress? (Large Agency/Small Independent Contractor)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A: This is a subjective question because the size and number of a dev team are relative. However, for this article’s sake, let’s just say there can be a single one-person developer, a small dev team is about 2-5, and anything above 5 is mid to large.&lt;/p&gt;

&lt;p&gt;The main thing to consider here is your environments as far as development, staging, and production. If you have a large developer team that is pushing numerous changes to your code, it is a necessity to ensure those changes are exactly what you wanted before going live. Isolating these changes in environments solves this.&lt;/p&gt;

&lt;p&gt;A single developer can set up a simplified workflow with maybe only one or two environments, plus local. The Local Sites tool is great for a single developer workflow.&lt;/p&gt;

&lt;p&gt;When a development team is small to large, there are three things to consider when planning a workflow – 1) Everyone is able to contribute, 2) Everyone has access to the most recent versions, and 3) All developers are able to iterate through CI/CD.&lt;/p&gt;

&lt;p&gt;For larger agency teams, especially when developers are switching between projects, you might want to use a containerized approach where you can create a repeatable/cloned environment that each team member can use.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Q: What if I want an efficient way to check my code on the browser without pushing it to production?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deployment previews in Atlas allow you to deploy a preview environment on a live URL without having to procure any isolated staging environment. It gives you a unique live site that is isolated off any feature branch you checkout. This will alleviate the issue of spinning up multiple staging environments and make your Atlas workflow better.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Q: What are some recommendations for making sure your front &amp;amp; backend deploy at the same time?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A: There are different ways you can approach the answer to this. For example, if you need to push changes to the WordPress backend to add and or remove functionality, and push changes to the frontend app synchronously to accommodate those backend changes, what is the best way to do so? 🤔&lt;/p&gt;

&lt;p&gt;Two main solutions that are most commonly used are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Push changes for the frontend &amp;amp; backend at the same time and don’t worry about the time variance. This would work for hobby sites, but not important ones where a page in an error state could mean an abandoned shopping cart, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Include fallback code and do it in stages. Push code to the frontend and backend that tries to use the new changes, but gracefully falls back to the old version if it’s not available. This approach could use a feature flag, check for a certain version number, etc. Then once you’re confident all users are on the new version of the frontend application, push another update to finally remove the old functionality.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing to note is that there really is no perfect solution to this question or any solution in Headless web development for that matter. The challenge of keeping your different code bases in sync from a timing perspective in this approach will have trade-offs on any option you choose and these are things that come with the territory. The key here is to just figure out which trade-offs mean more to you and your team. &lt;/p&gt;

&lt;p&gt;If you want a more granular example of these solutions to this question, we will have a deeper dive into it in an article to come soon!&lt;/p&gt;

&lt;h2&gt;
  
  
  Let us know your thoughts!
&lt;/h2&gt;

&lt;p&gt;Feedback is golden on a product for developers. If there are any features that you want to see on Atlas and WordPress as a Headless CMS, please let us know! Drop us a line in our discord channel. Stoked!!! 🎉&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>webdev</category>
      <category>wordpress</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Atlas Platform Features: Deployment Previews &amp; Webhooks</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Wed, 21 Sep 2022 15:33:19 +0000</pubDate>
      <link>https://dev.to/franadev/atlas-platform-features-deployment-previews-webhooks-74l</link>
      <guid>https://dev.to/franadev/atlas-platform-features-deployment-previews-webhooks-74l</guid>
      <description>&lt;p&gt;In this article, I will discuss the new Preview Environments and Webhook features in the &lt;a href="https://wpengine.com/atlas/"&gt;Atlas&lt;/a&gt; platform.  If you are new to Headless WordPress and would like to start testing out Atlas, sign up for a free sandbox account &lt;a href="https://wpengine.com/atlas/#atlasplans"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Modern Developer Workflow
&lt;/h2&gt;

&lt;p&gt;Modern web developers using a git-based approach to development expect certain features and functionality on their hosting platforms, especially ones that support their CI/CD (Continuous Integration and Continuous Delivery) workflows.  &lt;/p&gt;

&lt;p&gt;In the past, hosting platforms only allowed you to deploy sites to the same URL every time you pushed a change.  If you wanted to test changes before making them live on your production site, you had to manually create new sites or environments with new URLs to preview those changes. &lt;/p&gt;

&lt;p&gt;For example, in some workflows, you may have testing or staging branches tied to specific environments, and committing to those branches could help you automatically preview your changes before merging them into production. While you can still do this with Atlas environments, this Git flow strategy may be too rigid for some teams or conflict with other philosophies on repository management.&lt;/p&gt;

&lt;p&gt;Luckily, the Atlas platform has a solution for this issue that makes CI/CD less tedious and more flexible to implement. &lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Previews
&lt;/h2&gt;

&lt;p&gt;The Atlas team got me super stoked as we now have &lt;a href="https://developers.wpengine.com/docs/atlas/additional-guides/preview-environments"&gt;deployment previews!!&lt;/a&gt; This allows you and your team to see changes on your site without having to deploy them to your production site and without having to create a dedicated testing branch or environment.  &lt;/p&gt;

&lt;p&gt;The value for the modern developer's workflow is a less tedious, quicker way to enable better productivity.  As a developer, this helps you achieve constant collaboration and iteration for a quicker "ship more and ship fast flow."&lt;/p&gt;

&lt;h2&gt;
  
  
  How Deployment Previews Work
&lt;/h2&gt;

&lt;p&gt;Deployment previews work by deploying every pull request from your GitHub repository to a specific preview environment, with its own URL, logging, and tools, in relation to the feature branch you created.  This isolates each branch you make from your main production branch without having to manually spin up entirely new site environments!&lt;/p&gt;

&lt;p&gt;You and your team can view the unique URL to see how these changes look before they are merged into production and deployed.&lt;/p&gt;

&lt;p&gt;In order to enhance the workflow even more you can access information about the Atlas preview environment in two places:  the GitHub UI and the Atlas Platform UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mLcAGNbP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nplbtxk1363xedh4d1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mLcAGNbP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nplbtxk1363xedh4d1o.png" alt="Image description" width="880" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--apLTgybo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfis8u6i95eqjg7hw0zw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--apLTgybo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfis8u6i95eqjg7hw0zw.png" alt="Image description" width="880" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can link back and forth between UI's conveniently as well.&lt;/p&gt;

&lt;p&gt;As you iterate and develop your site, you can continuously push multiple changes, checkout multiple branches, and share these URLs for collaboration and feedback, in turn exponentially increasing overall developer and team productivity.  Under the hood, Atlas keeps up with your feature branch changes, builds the environment, and provides the unique URL! ⚡&lt;/p&gt;

&lt;p&gt;Please visit the preview environment &lt;a href="https://developers.wpengine.com/docs/atlas/additional-guides/preview-environments"&gt;docs&lt;/a&gt; for a step-by-step guide on usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment Previews FAQs
&lt;/h2&gt;

&lt;h4&gt;
  
  
  I Don't See Deployment Previews Anywhere in the Atlas Platform
&lt;/h4&gt;

&lt;p&gt;The deployment previews feature needs to be enabled on a per-environment basis, which means if you want to preview PRs submitted against your main or production branch, &lt;a href="//developers.wpengine.com/docs/atlas/additional-guides/preview-environments#enable-the--preview-environments--feature(opens%20in%20a%20new%20tab)"&gt;the setting needs to be enabled for that Atlas environment. &lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Do Deployment Previews Count Towards My Resources?
&lt;/h4&gt;

&lt;p&gt;No, any deployment previews that you create do not count towards your plan entitlements. &lt;/p&gt;

&lt;h4&gt;
  
  
  What Happens if I Make Additional Commits to a PR?
&lt;/h4&gt;

&lt;p&gt;If you continue to make commits to a feature branch with an open PR, Atlas will detect those changes and rebuild your deployment preview.&lt;/p&gt;

&lt;h4&gt;
  
  
  How Do I Delete a Deployment Preview?
&lt;/h4&gt;

&lt;p&gt;The main mechanism for managing preview environments is the PR lifecycle. If you close a PR or merge it into its target branch, those actions will automatically delete the deployment preview associated with the PR. &lt;/p&gt;

&lt;h2&gt;
  
  
  Atlas Environment Rebuild Webhooks
&lt;/h2&gt;

&lt;p&gt;The modern git-based workflow gets me JAMstoked because it enables quick continuous streamlined workflows with all things expected to be connected once things are spun up and pushed into your repository.&lt;/p&gt;

&lt;p&gt;Often times though, there are teams of content creators or other technical/non-technical stakeholders who are not immersed in the git-based flow but still need a way to push changes from the WordPress CMS to the front-end of their headless site. For example, if a site is using SSG, a content editor may want to rebuild a particular post or page whenever they update the resource in WordPress.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Webhook?
&lt;/h3&gt;

&lt;p&gt;This is where webhooks come into the workflow.  A webhook is an HTTP-based callback function that allows lightweight, event-driven communication between two APIs. &lt;/p&gt;

&lt;p&gt;In this case, the WP Engine Atlas platform features webhooks that allow users to trigger an environment rebuild from an event created by WordPress. (e.g. updating, deleting, adding posts)&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Builds with Atlas Webhooks
&lt;/h2&gt;

&lt;p&gt;The Atlas platform makes it really easy to generate a webhook to rebuild your environment via WordPress events.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Go into your Atlas account and on to the Atlas accounts home page.  Click on any app environment you choose:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FGPxuBp0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amon9fdgb6b1lpd8wss1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FGPxuBp0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amon9fdgb6b1lpd8wss1.png" alt="Image description" width="880" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt;  Once you choose an app environment and click on it, you will be taken to an environment settings page.  Click on the &lt;code&gt;Settings&lt;/code&gt; link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SG4X2JCs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ha1glvjtzme94i8rcg8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SG4X2JCs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ha1glvjtzme94i8rcg8e.png" alt="Image description" width="880" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3:  Clicking on the Settings link takes you to the Settings page.  In the lower half of the page, you will see a Environment &lt;code&gt;webhook&lt;/code&gt; pane.  Click the Create a &lt;code&gt;webhook&lt;/code&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sfvifukB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g84h24jznu6p1uue46et.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sfvifukB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g84h24jznu6p1uue46et.png" alt="Image description" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you click that, an endpoint will be generated like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6uEJYWkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35f730h2m68710xu8uc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6uEJYWkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35f730h2m68710xu8uc3.png" alt="Image description" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy that endpoint URL because we are going to need it for our WordPress backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embed the Webhook in WordPress
&lt;/h3&gt;

&lt;p&gt;Now that your webhook is created on the Atlas platform, go into your WP Admin.  From here, you can select a plugin that will help trigger your Atlas webhook based on an action you do in WordPress, such as adding a new post for example.  &lt;/p&gt;

&lt;p&gt;You can use any plugin you like and we have some other suggestions in our docs, but in this example, I will use WP Webhooks.  &lt;/p&gt;

&lt;p&gt;Go to the side menu of your WP Admin and hit &lt;code&gt;Plugins &amp;gt; Add&lt;/code&gt; &lt;code&gt;New&lt;/code&gt; and search for WP Webhooks.  It should be the first one you see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lQWCBfr3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ooqf0x8prjk6sxqlyel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lQWCBfr3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ooqf0x8prjk6sxqlyel.png" alt="Image description" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you install and activate the plugin, it will give you an option on the menu under Settings.  Click on WP Webhooks there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hLr85vuc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipwkjg506mfd6x7obc3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hLr85vuc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipwkjg506mfd6x7obc3w.png" alt="Image description" width="880" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Atlas Platform Features: Deployment Previews &amp;amp; Webhooks&lt;br&gt;
In this article, I will discuss the new Preview Environments and Webhook features in the Atlas platform.  If you are new to Headless WordPress and would like to start testing out Atlas, sign up for a free sandbox account here!&lt;/p&gt;

&lt;p&gt;The Modern Developer Workflow&lt;br&gt;
Modern web developers using a git-based approach to development expect certain features and functionality on their hosting platforms, especially ones that support their CI/CD (Continuous Integration and Continuous Delivery) workflows.  &lt;/p&gt;

&lt;p&gt;In the past, hosting platforms only allowed you to deploy sites to the same URL every time you pushed a change.  If you wanted to test changes before making them live on your production site, you had to manually create new sites or environments with new URLs to preview those changes. &lt;/p&gt;

&lt;p&gt;For example, in some workflows, you may have testing or staging branches tied to specific environments, and committing to those branches could help you automatically preview your changes before merging them into production. While you can still do this with Atlas environments, this Git flow strategy may be too rigid for some teams or conflict with other philosophies on repository management.&lt;/p&gt;

&lt;p&gt;Luckily, the Atlas platform has a solution for this issue that makes CI/CD less tedious and more flexible to implement. &lt;/p&gt;

&lt;p&gt;Deployment Previews&lt;br&gt;
The Atlas team got me super stoked as we now have deployment previews!! This allows you and your team to see changes on your site without having to deploy them to your production site and without having to create a dedicated testing branch or environment.  &lt;/p&gt;

&lt;p&gt;The value for the modern developer's workflow is a less tedious, quicker way to enable better productivity.  As a developer, this helps you achieve constant collaboration and iteration for a quicker "ship more and ship fast flow."&lt;/p&gt;

&lt;p&gt;How Deployment Previews Work&lt;br&gt;
Deployment previews work by deploying every pull request from your GitHub repository to a specific preview environment, with its own URL, logging, and tools, in relation to the feature branch you created.  This isolates each branch you make from your main production branch without having to manually spin up entirely new site environments!&lt;/p&gt;

&lt;p&gt;You and your team can view the unique URL to see how these changes look before they are merged into production and deployed.&lt;/p&gt;

&lt;p&gt;In order to enhance the workflow even more you can access information about the Atlas preview environment in two places:  the GitHub UI and the Atlas Platform UI:&lt;/p&gt;

&lt;p&gt;This image has an empty alt attribute; its file name is Screen-Shot-2022-09-07-at-4.42.17-PM-1024x575.png&lt;br&gt;
This image has an empty alt attribute; its file name is Screen-Shot-2022-09-07-at-4.45.12-PM-1024x452.png&lt;br&gt;
You can link back and forth between UI's conveniently as well.  &lt;/p&gt;

&lt;p&gt;As you iterate and develop your site, you can continuously push multiple changes, checkout multiple branches, and share these URLs for collaboration and feedback, in turn exponentially increasing overall developer and team productivity.  Under the hood, Atlas keeps up with your feature branch changes, builds the environment, and provides the unique URL! ⚡&lt;/p&gt;

&lt;p&gt;Please visit the preview environment docs for a step-by-step guide on usage.&lt;/p&gt;

&lt;p&gt;Deployment Previews FAQs&lt;br&gt;
I Don't See Deployment Previews Anywhere in the Atlas Platform&lt;br&gt;
The deployment previews feature needs to be enabled on a per-environment basis, which means if you want to preview PRs submitted against your main or production branch, the setting needs to be enabled for that Atlas environment. &lt;/p&gt;

&lt;p&gt;Do Deployment Previews Count Towards My Resources?&lt;br&gt;
No, any deployment previews that you create do not count towards your plan entitlements. &lt;/p&gt;

&lt;p&gt;What Happens if I Make Additional Commits to a PR?&lt;br&gt;
If you continue to make commits to a feature branch with an open PR, Atlas will detect those changes and rebuild your deployment preview.&lt;/p&gt;

&lt;p&gt;How Do I Delete a Deployment Preview?&lt;br&gt;
The main mechanism for managing preview environments is the PR lifecycle. If you close a PR or merge it into its target branch, those actions will automatically delete the deployment preview associated with the PR. &lt;/p&gt;

&lt;p&gt;Atlas Environment Rebuild Webhooks&lt;br&gt;
The modern git-based workflow gets me JAMstoked because it enables quick continuous streamlined workflows with all things expected to be connected once things are spun up and pushed into your repository.&lt;/p&gt;

&lt;p&gt;Often times though, there are teams of content creators or other technical/non-technical stakeholders who are not immersed in the git-based flow but still need a way to push changes from the WordPress CMS to the front-end of their headless site. For example, if a site is using SSG, a content editor may want to rebuild a particular post or page whenever they update the resource in WordPress.&lt;/p&gt;

&lt;p&gt;What is a Webhook?&lt;br&gt;
This is where webhooks come into the workflow.  A webhook is an HTTP-based callback function that allows lightweight, event-driven communication between two APIs. &lt;/p&gt;

&lt;p&gt;In this case, the WP Engine Atlas platform features webhooks that allow users to trigger an environment rebuild from an event created by WordPress. (e.g. updating, deleting, adding posts)&lt;/p&gt;

&lt;p&gt;Configuring Builds with Atlas Webhooks &lt;br&gt;
The Atlas platform makes it really easy to generate a webhook to rebuild your environment via WordPress events.  &lt;/p&gt;

&lt;p&gt;Step 1: Go into your Atlas account and on to the Atlas accounts home page.  Click on any app environment you choose:&lt;/p&gt;

&lt;p&gt;This image has an empty alt attribute; its file name is Screen-Shot-2022-09-17-at-12.17.07-PM-1024x448.png&lt;br&gt;
Step 2:  Once you choose an app environment and click on it, you will be taken to an environment settings page.  Click on the Settings link:&lt;/p&gt;

&lt;p&gt;This image has an empty alt attribute; its file name is Screen-Shot-2022-09-20-at-10.21.01-AM-1024x580.png&lt;br&gt;
Step 3:  Clicking on the Settings link takes you to the Settings page.  In the lower half of the page, you will see an Environment webhook pane.  Click the Create a webhook button:&lt;/p&gt;

&lt;p&gt;This image has an empty alt attribute; its file name is Screen-Shot-2022-09-17-at-12.25.30-PM-1024x519.png&lt;br&gt;
Once you click that, an endpoint will be generated like this:&lt;/p&gt;

&lt;p&gt;This image has an empty alt attribute; its file name is Screen-Shot-2022-09-17-at-12.29.54-PM-1024x519.png&lt;br&gt;
Copy that endpoint URL because we are going to need it for our WordPress backend.&lt;/p&gt;

&lt;p&gt;Embed the Webhook in WordPress&lt;br&gt;
Now that your webhook is created on the Atlas platform, go into your WP Admin.  From here, you can select a plugin that will help trigger your Atlas webhook based on an action you do in WordPress, such as adding a new post for example.  &lt;/p&gt;

&lt;p&gt;You can use any plugin you like and we have some other suggestions in our docs, but in this example, I will use WP Webhooks.  &lt;/p&gt;

&lt;p&gt;Go to the side menu of your WP Admin and hit Plugins &amp;gt; Add New and search for WP Webhooks.  It should be the first one you see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mcFhp-4R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9l8qtcgdcxl2nogpvii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mcFhp-4R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9l8qtcgdcxl2nogpvii.png" alt="Image description" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you install and activate the plugin, it will give you an option on the menu under Settings.  Click on WP Webhooks there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--obzFutDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqpq6828mwlzf1kz0hbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--obzFutDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqpq6828mwlzf1kz0hbi.png" alt="Image description" width="880" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will bring you to the WP Webhooks main page.  In this case, we want to send data via a POST request to the Atlas platform when the webhook is triggered, so click on the Send Data option at the top: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7iiygFVZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frs48ojz1525jq6mq7vv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7iiygFVZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frs48ojz1525jq6mq7vv.png" alt="Image description" width="880" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you will have options on what actions you want to associate with your webhook trigger.  Let's use Post updated.  Once you choose the action, click on Add Webhook URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0e2juH5B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5sin6r30xt9jyaeyon4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0e2juH5B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5sin6r30xt9jyaeyon4.png" alt="Image description" width="880" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you select those options, you will see a modal pop-up that will ask you to name your webhook and embed the webhook you created from Atlas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bsDeJf-9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjqxyzgvlnstqumphnbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bsDeJf-9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjqxyzgvlnstqumphnbd.png" alt="Image description" width="880" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And done!!! The next thing to do is go and create a new post.  When you hit publish on that post, you can go to your Atlas account in WP Engine and see that a build was triggered due to the action in your WordPress backend!! Stoked! 🎉&lt;/p&gt;

&lt;p&gt;This feature here allows the modern web teams the ability to update data without having to touch code or jump into a command line.&lt;/p&gt;

&lt;p&gt;Please reference the &lt;a href="https://developers.wpengine.com/docs/atlas/customization/builds#rebuild-an-environment-via-webhook"&gt;docs&lt;/a&gt; as well for webhooks.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Stay Tuned, more Atlas features to come...
&lt;/h2&gt;

&lt;p&gt;These two features are table stakes that modern jamstack/decoupled hosting platforms should have.  The addition of these features to the Atlas platform will improve your modern web dev workflow across all your teams and stakeholders.&lt;/p&gt;

&lt;p&gt;Stay tuned for more Atlas features to come!  As always let us know your feedback, thoughts, and ideas in &lt;a href="https://discord.com/invite/J2khkF9XYK"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>webdev</category>
      <category>wordpress</category>
      <category>headless</category>
    </item>
    <item>
      <title>Sitemaps in Headless WordPress with Next.js</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Mon, 12 Sep 2022 17:14:31 +0000</pubDate>
      <link>https://dev.to/franadev/sitemaps-in-headless-wordpress-with-nextjs-3li8</link>
      <guid>https://dev.to/franadev/sitemaps-in-headless-wordpress-with-nextjs-3li8</guid>
      <description>&lt;p&gt;In this tutorial, you will learn how to create sitemaps in Headless WordPress using Next.js and the WP Sitemap Rest API plugin. (Just a note, if you need a foundational understanding of Next.js, please follow this &lt;a href="https://developers.wpengine.com/blog/crash-course-build-a-simple-headless-wordpress-app-with-next-js-wpgraphql" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; by my main man Jeff Everhart.&lt;/p&gt;

&lt;p&gt;Previously, I wrote about what Sitemaps are and the benefits of them in this article you can reference&lt;a href="https://developers.wpengine.com/blog/sitemaps-in-headless-wordpress-with-faust-js" rel="noopener noreferrer"&gt; here&lt;/a&gt;. In the context of this blog post, we will jump right into creating the sitemaps. By the end of this tutorial you will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a Sitemap in Next.js that dynamically pulls your URLs from WordPress&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add your Next.js pages and paths on your Sitemap including dynamic routes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup your WordPress Site
&lt;/h2&gt;

&lt;p&gt;The first thing we are going to do is configure your WordPress site so that we have access to the particular endpoints for our Sitemaps. We are going to use the WP Sitemap Rest API plugin. Download the plugin from the repo via zip file. Next, In WP Admin, go to the &lt;code&gt;Plugins &amp;gt; Add&lt;/code&gt; &lt;code&gt;Plugins&lt;/code&gt; option at the top right and upload the plugin.&lt;/p&gt;

&lt;p&gt;Once you install that plugin, you expose these 4 endpoints to your &lt;code&gt;REST API&lt;/code&gt; in WordPress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/wp-json/sitemap/v1/totalpages
/wp-json/sitemap/v1/author?pageNo=1&amp;amp;perPage=1000
/wp-json/sitemap/v1/taxonomy?pageNo=1&amp;amp;perPage=1000&amp;amp;taxonomyType=category or tag
/wp-json/sitemap/v1/posts?pageNo=1&amp;amp;perPage=1000&amp;amp;postType=post or page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easily, with this plugin, if you visit &lt;code&gt;“your.wordpresssite.com”&lt;/code&gt;plus appending any of the 4 endpoints, you will get an object back of what those paths reflect. An example of my demo site with the first endpoint added:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaviph856vpgks57krec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsaviph856vpgks57krec.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Easy Peasy, now on to the Next.js frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Dependencies
&lt;/h2&gt;

&lt;p&gt;Before we go into the code, we need to install a dependency we will use in this tutorial within our Next.js project called &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt;. Axios will give us some helper functionality to make &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" rel="noopener noreferrer"&gt;XMLHttpRequests&lt;/a&gt; from our Next.js front-end. To do this, run npm i axios in your terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;There are a couple of endpoints we will have to set up as environment variables in our project before we create the functions needed.&lt;/p&gt;

&lt;p&gt;In the root of your project, create a &lt;code&gt;.env.local&lt;/code&gt; file. In this file, add your endpoints and how many items per sitemap you would want. Note that on the frontend URL, because I am using a development environment on my local machine, I am using the Next.js default of localhost with port 3000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_WORDPRESS_API_URL=https://yourwordpresssite.com
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
NEXT_PUBLIC_ITEM_PER_SITEMAP=50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have added our environment variables in the properly named file, let’s allow our project access to them via the &lt;code&gt;process.env&lt;/code&gt;object.&lt;/p&gt;

&lt;p&gt;In the root of your project, create a &lt;code&gt;utils&lt;/code&gt; folder. In the &lt;code&gt;utils&lt;/code&gt; folder, create a file called variables.js. In this file, add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const wordpressUrl = process.env.NEXT_PUBLIC_WORDPRESS_URL;
export let frontendUrl = process.env.NEXT_PUBLIC_FRONTEND_URL;
export let sitemapPerPage = process.env.NEXT_PUBLIC_ITEM_PER_SITEMAP || 50; //1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variables we are naming above are needed so that Next.js can access those endpoints from our&lt;code&gt;.env.local&lt;/code&gt; with the &lt;code&gt;process.env&lt;/code&gt; object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js sitemap functions
&lt;/h2&gt;

&lt;p&gt;We are going to need a few functions in order to dynamically grab the total amount of WordPress URLs to make them available to your sitemap index page and show them on the browser as well as showing our Next.js front-end URL’s.&lt;/p&gt;

&lt;p&gt;The first step is to create a folder called &lt;code&gt;lib&lt;/code&gt; in the root of the project. In the new &lt;code&gt;lib&lt;/code&gt; folder, create a file called &lt;code&gt;getTotalCounts.js&lt;/code&gt; and add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from "axios";
import { wordpressUrl } from "../utils/variables";

export default async function getTotalCounts() {
  const res = await axios.get(`${wordpressUrl}/wp-json/sitemap/v1/totalpages`);
  let data = await res.data;
  if (!data) return [];
  const propertyNames = Object.keys(data);
  let excludeItems = ["user"];
  //if you want to remove any item from sitemap, add it to excludeItems array
  let totalArray = propertyNames
    .filter((name) =&amp;gt; !excludeItems.includes(name))
    .map((name) =&amp;gt; {
      return { name, total: data[name] };
    });

  return totalArray;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is a fetch function that is grabbing the total pages from your WordPress site and returning that data in an array. If you want to exclude any data from your sitemap you can add it to the &lt;code&gt;excludeItems&lt;/code&gt; array as commented.&lt;/p&gt;

&lt;p&gt;The next thing we need to create is a way to get the sitemap pages and return them for the specific type.&lt;/p&gt;

&lt;p&gt;Back in our &lt;code&gt;utils&lt;/code&gt; folder, create a file called &lt;code&gt;getSitemapPages.js&lt;/code&gt; and add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { frontendUrl, sitemapPerPage } from "./variables";

export default function getSitemapPages(item) {
  const items = [];
  for (let i = 1; i &amp;lt;= Math.ceil(item.total / sitemapPerPage); i++) {
    let url = `${frontendUrl}/sitemap/${item.name}_sitemap${i}.xml`;
    items.push(
      ` 
        &amp;lt;sitemap&amp;gt;
           &amp;lt;loc&amp;gt;
              ${url}
          &amp;lt;/loc&amp;gt;
      &amp;lt;/sitemap&amp;gt;
      `
    );
  }
  return items.join("");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is importing the variables we declared in our &lt;code&gt;variables.js&lt;/code&gt; file. Then, it exports a function by default that is passing in the item from the total counts function we created. Once these are passed in, the front-end URL is appended with the item name and the XML path. Then we join these with the XML tags with the slug from WordPress.&lt;/p&gt;

&lt;p&gt;We now have the necessary functions in place to generate our sitemap pages and the total number of them. The next step is to create a sitemap index page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sitemap Index Page
&lt;/h2&gt;

&lt;p&gt;We are going to take advantage of Next.js and its page-based file routing.  This means that any file I add to the pages directory in my project will correspond automatically to a route that is available. &lt;/p&gt;

&lt;p&gt;Create a file in the pages directory of your project called &lt;code&gt;sitemap.xml.js&lt;/code&gt; and add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import getSitemapPages from "../utils/getSitemapPages";
import getTotalCounts from "../lib/getTotalCounts";
export default function SitemapIndexPage() {
  return null;
}
export async function getServerSideProps({ res }) {
  const details = await getTotalCounts();

  let sitemapIndex = `&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;
  &amp;lt;sitemapindex xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd"
           xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&amp;gt;
     ${details.map((item) =&amp;gt; getSitemapPages(item)).join("")}
  &amp;lt;/sitemapindex&amp;gt;`;
  res.setHeader("Content-Type", "text/xml; charset=utf-8");
  res.setHeader(
    "Cache-Control",
    "public, s-maxage=600, stale-while-revalidate=600"
  );
  res.write(sitemapIndex);
  res.end();
  return { props: {} };
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing to notice at the top of the file is the importing of the two functions we just created to generate the WordPress page types and the total counts. Then we have this page as a default function that will utilize server-side rendering since we want this page to be dynamic on every request so the sitemap is up to date.&lt;/p&gt;

&lt;p&gt;Next, I copied a sitemap example and formatted it so that the XML tags and links show up on the browser with the response header set and the page write response set to load and end with the props returned.&lt;/p&gt;

&lt;p&gt;Let’s get stoked now and see what this looks like on the browser. Go to your terminal and run &lt;code&gt;npm run dev&lt;/code&gt;. You should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8gll11dgzvltb5f36l6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8gll11dgzvltb5f36l6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome!! We now have a dynamic sitemap in a headless WordPress setup. The next step is to add the ability to grab the URL of the sitemap path and have that route to its detail page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic Sitemap Page Routes
&lt;/h2&gt;

&lt;p&gt;We need a couple of things to make the dynamic page routes work for our sitemap. First, we need a way to generate the sitemap paths to their detail page.&lt;/p&gt;

&lt;p&gt;Go back to the utils folder we created earlier. In this folder, create a &lt;code&gt;generateSitemapPaths.js&lt;/code&gt; file and add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { frontendUrl } from "./variables";

export default function generateSitemapPaths(array) {
  const items = array.map(
    (item) =&amp;gt;
      `
            &amp;lt;url&amp;gt;
                &amp;lt;loc&amp;gt;${frontendUrl + item?.url}&amp;lt;/loc&amp;gt;
                ${
                  item?.post_modified_date
                    ? `&amp;lt;lastmod&amp;gt;${
                        new Date(item?.post_modified_date)
                          .toISOString()
                          .split("T")[0]
                      }&amp;lt;/lastmod&amp;gt;`
                    : ""
                }
            &amp;lt;/url&amp;gt;
            `
  );
  return items.join("");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file imports our front-end URL variable and then exports a default function that passes in an array. The items variable is mapped which then returns a single item. Then we have XML tags that dynamically grab the appropriate item and concatenate that to your frontend URL, as well as the date the page was last modified. Now that we have the function needed, let’s create the dynamic route page.&lt;/p&gt;

&lt;p&gt;Next.js makes it easy to create a dynamic file if you have a parameter like a &lt;code&gt;slug&lt;/code&gt; that changes upon request. The naming convention for this is the bracket syntax. Create a folder in the pages directory called &lt;code&gt;sitemap&lt;/code&gt;. In this folder, create a file called &lt;code&gt;[slug].js&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j777ira6s2vx5a51uor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j777ira6s2vx5a51uor.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;[slug].js&lt;/code&gt; file, add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import getSitemapPageUrls from "../../lib/getSitemapPageUrls";
import getTotalCounts from "../../lib/getTotalCounts";
import generateSitemapPaths from "../../utils/generateSitemapPaths";
export default function SitemapTagPage() {
  return null;
}
export async function getServerSideProps({ res, params: { slug } }) {
  let isXml = slug.endsWith(".xml");
  if (!isXml) {
    return {
      notFound: true,
    };
  }
  let slugArray = slug.replace(".xml", "").split("_");
  let type = slugArray[0];
  let pageNo = slugArray[1]?.match(/(\d+)/)[0] ?? null;
  let page = pageNo ? parseInt(pageNo) : null;
  let possibleTypes = await getTotalCounts();
  if (!possibleTypes.some((e) =&amp;gt; e.name === type)) {
    return {
      notFound: true,
    };
  }
  let pageUrls = await getSitemapPageUrls({ type, page });
  if (!pageUrls?.length) {
    return {
      notFound: true,
    };
  }
  let sitemap = `&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
  &amp;lt;urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&amp;gt;
    ${generateSitemapPaths(pageUrls)}
  &amp;lt;/urlset&amp;gt;`;
  res.setHeader("Content-Type", "text/xml; charset=utf-8");
  res.setHeader(
    "Cache-Control",
    "public, s-maxage=600, stale-while-revalidate=600"
  );
  res.write(sitemap);
  res.end();
  return { props: {} };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is quite a bit of code in this file. Essentially, what is happening in this file is that we are importing all of our functions at the top that we need access to for the sitemap dynamic paths.&lt;/p&gt;

&lt;p&gt;Next, a default function is exported, calling it &lt;code&gt;SitemapTagPage&lt;/code&gt;. We return null, then have an async function to &lt;code&gt;getServerSideProps&lt;/code&gt; as we want this function to be server-side rendered as well on every request. Then in the response object, we pass in the params which is the slug in this case.&lt;/p&gt;

&lt;p&gt;Our variable &lt;code&gt;isXML&lt;/code&gt; is equal to the &lt;code&gt;slug&lt;/code&gt; and the appended XML path ending. If this a not an xml slug and path we return it to not be found.&lt;/p&gt;

&lt;p&gt;In the middle of the file, we declare numerous variables that we name as we need to set the array of slugs, the type, the page number using regex which extracts the number from the string, and the possible types and the total counts of those types.&lt;/p&gt;

&lt;p&gt;Lastly, we validate this to see if it properly is aligned with our sitemap index page, otherwise, we throw a &lt;code&gt;404&lt;/code&gt;. Once that is done, we have our &lt;code&gt;XML&lt;/code&gt; tags formatted and the appropriate responses.&lt;/p&gt;

&lt;p&gt;Let’s try this now and see if this works in the browser. Go back to the terminal and run &lt;code&gt;npm run dev&lt;/code&gt;. Once the sitemap index page shows on the browser, grab any &lt;code&gt;URL&lt;/code&gt; on the &lt;br&gt;
sitemap and put it in the search bar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5zy9e3jbbj4p82b21ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5zy9e3jbbj4p82b21ga.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose to grab my post page URL at &lt;code&gt;sitemap/post_sitemap1.xml&lt;/code&gt;. I followed that link in the search bar and got the post sitemap details! Stoked!! 🎉&lt;/p&gt;
&lt;h2&gt;
  
  
  Next.js Pages
&lt;/h2&gt;

&lt;p&gt;Finally, we need to account for our Next.js pages on our front-end. In order to do this, let’s go to our pages directory and add a file called &lt;code&gt;sitemapnextjs.xml.js&lt;/code&gt;. In this file, add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import * as fs from "fs";
const Sitemap = () =&amp;gt; {
  return null;
};

export const getServerSideProps = async ({ res }) =&amp;gt; {
  const BASE_URL = "http://localhost:3000";

  const staticPaths = fs
    .readdirSync("pages")
    .filter((staticPage) =&amp;gt; {
      return ![
        "api",
        "about",
        "_app.js",
        "_document.js",
        "404.js",
        "sitemap.xml.js",
      ].includes(staticPage);
    })
    .map((staticPagePath) =&amp;gt; {
      return `${BASE_URL}/${staticPagePath}`;
    });

  const dynamicPaths = [`${BASE_URL}/name/1`, `${BASE_URL}/name/2`];

  const allPaths = [...staticPaths, ...dynamicPaths];

  const sitemap = `&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
    &amp;lt;urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&amp;gt;
      ${allPaths
        .map((url) =&amp;gt; {
          return `
            &amp;lt;url&amp;gt;
              &amp;lt;loc&amp;gt;${url}&amp;lt;/loc&amp;gt;
              &amp;lt;lastmod&amp;gt;${new Date().toISOString()}&amp;lt;/lastmod&amp;gt;
              &amp;lt;changefreq&amp;gt;monthly&amp;lt;/changefreq&amp;gt;
              &amp;lt;priority&amp;gt;1.0&amp;lt;/priority&amp;gt;
            &amp;lt;/url&amp;gt;
          `;
        })
        .join("")}
    &amp;lt;/urlset&amp;gt;
  `;

  res.setHeader("Content-Type", "text/xml");
  res.write(sitemap);
  res.end();

  return {
    props: {},
  };
};

export default Sitemap;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top of the file, we are importing React, and fs to access local storage. Next, we return null as we want this to not render or return anything in the component. Instead, we will use the &lt;code&gt;getServerSideProps&lt;/code&gt; function, export it as a const, then pass in the response object, just like we did in the sitemap index page to server-side render for fresh, up-to-date sitemap data.&lt;/p&gt;

&lt;p&gt;Then we declare our base URL variable, in this case, I am using the dev server off &lt;code&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we set the fs to our static paths, we enable a way within the object to filter through our static pages in Next.js within an array and input which ones we do not want on the sitemap. I just added the default pages Next.js comes with as an example.&lt;/p&gt;

&lt;p&gt;The next step is to account for the dynamic paths that you have in Next.js, apart from WordPress. We set a const for our dynamic paths and make it equal to an array where we get our base URL and our hard coded path. (I just named it “name” but you can name it in relation to whatever your dynamic route file is)&lt;/p&gt;

&lt;p&gt;The last things we need to do are to set the const for all our paths and iterate through them with the spread operator and expand all of them in an array.&lt;/p&gt;

&lt;p&gt;Finally, we generate the sitemap in XML format with the appropriate response headers returning our props. Jamstoked!! We now have all the paths and the URLS in our Next.js front-end!!! 💥&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining Next.js pages and WordPress pages in your sitemap index page
&lt;/h2&gt;

&lt;p&gt;All that is left is adding our Next.js pages to our sitemap index with our WordPress pages. Go back to the sitemap.xml.js file and add this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import getSitemapPages from "../utils/getSitemapPages";
import getTotalCounts from "../lib/getTotalCounts";
export default function SitemapIndexPage() {
  return null;
}
export async function getServerSideProps({ res }) {
  const details = await getTotalCounts();

  let sitemapIndex = `&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;
  &amp;lt;sitemapindex xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd"
           xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"&amp;gt;
     ${details.map((item) =&amp;gt; getSitemapPages(item)).join("")}
     &amp;lt;sitemap&amp;gt;
     &amp;lt;loc&amp;gt;
        http://localhost:3000/sitemapnextjs.xml
    &amp;lt;/loc&amp;gt;
&amp;lt;/sitemap&amp;gt;
  &amp;lt;/sitemapindex&amp;gt;`;
  res.setHeader("Content-Type", "text/xml; charset=utf-8");
  res.setHeader(
    "Cache-Control",
    "public, s-maxage=600, stale-while-revalidate=600"
  );
  res.write(sitemapIndex);
  res.end();
  return { props: {} };
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one difference in this file is the URL we added &lt;code&gt;http://localhost:3000/sitemapnextjs.xml&lt;/code&gt; in our XML element &lt;code&gt;&amp;lt;loc&amp;gt;&amp;lt;/loc&amp;gt;&lt;/code&gt;. This will allow us to show the Next.js pages on our sitemap index.&lt;/p&gt;

&lt;p&gt;Let’s see this in action in the browser. Go back to terminal and run npm run dev. You should see this in the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0oj3n5nozt0lw4zhtf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0oj3n5nozt0lw4zhtf7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now have our sitemap for Next.js pages on our index. Let’s see what all the details of our pages are in Next.js when we follow that URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faijr84vy0ukr3g6b9rlv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faijr84vy0ukr3g6b9rlv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Done!! Jamstoked! 🎉
&lt;/h2&gt;

&lt;p&gt;We are finished with this walk-through tutorial! Jamstoke! With this tutorial, we hope you take away the ability to understand the importance of sitemaps and the value of utilizing them to inform search engines of your site’s page and path layout.&lt;/p&gt;

&lt;p&gt;This is just one way of approaching sitemaps in headless WordPress. I would love to hear your thoughts and your own approaches and builds. Hit me up in our Discord!&lt;/p&gt;

&lt;p&gt;Here is the demo once again so you can follow along, make it your own and add on to it if you like on my &lt;a href="https://github.com/Fran-A-Dev/sitemap-headlesswp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I want to give a major shoutout and credit to &lt;a href="https://dipankarmaikap.com/" rel="noopener noreferrer"&gt;Dipankar Maikap&lt;/a&gt; who created the WP Sitemap Rest Api plugin and assisted me in the creation of this tutorial.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>wordpress</category>
      <category>sitemaps</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sitemaps in Headless WordPress with Faust.js</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Fri, 02 Sep 2022 14:13:52 +0000</pubDate>
      <link>https://dev.to/franadev/sitemaps-in-headless-wordpress-with-faustjs-2e6n</link>
      <guid>https://dev.to/franadev/sitemaps-in-headless-wordpress-with-faustjs-2e6n</guid>
      <description>&lt;p&gt;In this article, I will discuss Sitemaps in Headless WordPress using the open source framework for Headless WordPress – Faust.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  XML Sitemaps
&lt;/h2&gt;

&lt;p&gt;An XML sitemap is a list of URLs that you want to be publicly available. It helps search engines crawl your website by giving them an exact map of all your content. There is also metadata you can pull into your sitemap which are listed in the &lt;a href="https://www.sitemaps.org/protocol.html"&gt;sitemaps protocol&lt;/a&gt; such as when a page was last modified, video information and images.&lt;/p&gt;

&lt;p&gt;Below is an image of a traditional WordPress XML sitemap url. The default is your WordPress URL with the added path &lt;code&gt;/wp-sitemap.xml&lt;/code&gt; and this is baked into WordPress core from version 5 and above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_9aHDF-5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fvkak10b3vnc6jl4n1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_9aHDF-5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fvkak10b3vnc6jl4n1n.png" alt="Image description" width="880" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;XML Sitemap Benefits&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster Indexing&lt;/strong&gt;: XML sitemaps will communicate with search engines. You can submit your sitemap into the Google Search Console and that helps get pages on your site indexed much faster with search engines. This increases your rank in search engine page results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic Update Notification&lt;/strong&gt;: Google will receive automatic notifications when you publish new content or modify existing content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Categorization of Your Content&lt;/strong&gt;: Allows Google to know which pages go under which category so there is no guesswork.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  XML Sitemaps in the Faust.js framework
&lt;/h2&gt;

&lt;p&gt;Sitemaps can be complicated to create and process when you are using WordPress in a headless architecture. Thankfully, the Faust.js team at WP Engine created a feature within the framework that makes it work within a single file within the pages directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;The first step is to install the &lt;a href="https://faustjs.org/"&gt;Faust.js framework&lt;/a&gt; with the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app \
    -e https://github.com/wpengine/faustjs/tree/main \
    --example-path examples/next/getting-started \
    --use-npm \
    my-app
cd my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Copy the sample environment template&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp .env.local.sample .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Connect your WordPress site&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Navigate to your &lt;code&gt;.env.local&lt;/code&gt; file in the root of your project and paste your WP URL as the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Your WordPress site URL
NEXT_PUBLIC_WORDPRESS_URL=https://headlessfw.wpengine.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Setting Permalinks and Reading Visibility&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Set permalinks In &lt;code&gt;WP Admin &amp;gt;&amp;gt; Settings &amp;gt;&amp;gt; Permalinks&lt;/code&gt; to: &lt;code&gt;/posts/%postname%/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VPbJGlrX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nvz1niyzqgm7rjt3rm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VPbJGlrX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nvz1niyzqgm7rjt3rm8.png" alt="Image description" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set reading settings in &lt;code&gt;WP Admin &amp;gt;&amp;gt; Settings &amp;gt;&amp;gt; Reading &amp;gt;&amp;gt;&lt;/code&gt; Search Engine Visibility uncheck box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YTN6y3-w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u96i4uvlppj9m7pdg0xw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YTN6y3-w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u96i4uvlppj9m7pdg0xw.png" alt="Image description" width="880" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Faust.js
&lt;/h2&gt;

&lt;p&gt;Next, navigate back to your Faust.js app, and in the &lt;code&gt;src&lt;/code&gt;folder at the projects root, go to the &lt;code&gt;/pages&lt;/code&gt; directory. Within the pages directory, create a file and name it &lt;br&gt;
&lt;code&gt;sitemap.xml.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7tKtatia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdk7ey2k4uyszoja53rr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7tKtatia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdk7ey2k4uyszoja53rr.png" alt="Image description" width="880" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;sitemap.xml.tsx&lt;/code&gt; you just created, copy and paste this code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { getSitemapProps } from '@faustjs/next/server';
import { GetServerSidePropsContext } from 'next';

export default function Sitemap() {}

export function getServerSideProps(ctx: GetServerSidePropsContext) {
  return getSitemapProps(ctx, {
    sitemapIndexPath: '/wp-sitemap.xml',
    wpUrl: 'https://buddydemo1.wpengine.com',
    frontendUrl: 'http://localhost:3000',
  });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, the first thing that is happening is the importing of the sitemap properties on the Faust.js server framework as well as importing the Server Side properties context from Next.js.&lt;/p&gt;

&lt;p&gt;Next, we export a default function that is the Sitemap with an empty object. Once that is done, below is a function in Next.js that allows for server side rendering. Within this function, we pass in the server side props context and then return a config object with the sitemap props.&lt;/p&gt;

&lt;p&gt;The config object requires your &lt;code&gt;sitemapIndexPath&lt;/code&gt; property with a value of your WordPress &lt;code&gt;sitemap.xml&lt;/code&gt; path, a &lt;code&gt;wpURL&lt;/code&gt; property with a value of your WordPress URL, and your Faust.js frontend URL. In this case I am developing locally of &lt;code&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that I have this file setup in my pages directory within Faust, I can run &lt;code&gt;npm run dev&lt;/code&gt; to start the dev server and I am able to access my WordPress content dynamically to my Headless site. Stoked!! 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RMvXq9FR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sh50rrvlmzua4q8binl0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RMvXq9FR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sh50rrvlmzua4q8binl0.png" alt="Image description" width="880" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Next.js Pages for Sitemaps
&lt;/h2&gt;

&lt;p&gt;The last thing we need to account for in our sitemap is the Next.js pages. Since we are using Headless WordPress, the front-end URLs need to be on the sitemap as well. Within the same &lt;code&gt;sitemap.xml.tsx&lt;/code&gt; file, simply add a pages property array and place the relative paths of Next.js in the objects within the array like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { getSitemapProps } from '@faustjs/next/server';
import { GetServerSidePropsContext } from 'next';

export default function Sitemap() {}

export function getServerSideProps(ctx: GetServerSidePropsContext) {
  return getSitemapProps(ctx, {
    sitemapIndexPath: '/wp-sitemap.xml',
    wpUrl: 'https://buddydemo1.wpengine.com',
    frontendUrl: 'http://localhost:3000',
    pages: [
      {
        path: '/about',
        changefreq: 'monthly',
      },
      {
        path: '/',
      },
    ],
  });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running my development server again and visiting the sitemap URL, I now have my WordPress content and my Next.js content in faust-pages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GEuj8oif--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chjxmlrlir1k1obuk9sz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GEuj8oif--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chjxmlrlir1k1obuk9sz.png" alt="Image description" width="880" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Super Jamstoked!! ⚡ of the many benefits of using a framework like Faust.js is the developer toil it removes from your plate especially in Headless WordPress. Features like sitemaps should just work simply out of the box with the ability to account for your URLs from WordPress and your front-end without too much heavy lifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Done! What Next?
&lt;/h2&gt;

&lt;p&gt;If Faust.js is not your thing, stay tuned, I shall create another blog-post in relation to this but I will create a XML sitemap using only Next.js and Headless WordPress!!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>wordpress</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Efficient SSG in Next.js with WPGraphQL</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Thu, 04 Aug 2022 15:41:00 +0000</pubDate>
      <link>https://dev.to/franadev/efficient-ssg-in-nextjs-with-wpgraphql-45bi</link>
      <guid>https://dev.to/franadev/efficient-ssg-in-nextjs-with-wpgraphql-45bi</guid>
      <description>&lt;p&gt;In this article, I will discuss best practices around Static Site Generation in Next.js with dynamic routes and static paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Site Generation
&lt;/h2&gt;

&lt;p&gt;Before I dive into optimization details, let's go over quickly for context what Static Site Generation (or SSG for short) does and how it works with &lt;code&gt;getStaticPaths&lt;/code&gt; in dynamic routes.&lt;/p&gt;

&lt;p&gt;Next.js allows you to statically generate your site, pages and fetch data at build time with the function &lt;code&gt;getStaticProps&lt;/code&gt;.  The main reason developers choose this method is speed and performance as the static files and data are cached and served on a CDN and available right at request. &lt;/p&gt;

&lt;h2&gt;
  
  
  Static Paths and Dynamic Routes
&lt;/h2&gt;

&lt;p&gt;When you have a site that is statically generated but you have a selection of posts on a home page and want users to be able to click that post which will route them to the details page of that individual post, you will need a route parameter for the route for that individual details page of the post.  Now, Next.js does not know how many individual details pages we have and the routes associated with those pages since it depends on external data, in this case WordPress is our external data source.&lt;/p&gt;

&lt;p&gt;We can explicitly tell Next.js what pages and routes we need to create at build time based on our WordPress data.  To do this, we use the function called &lt;code&gt;getStaticPaths&lt;/code&gt;.  This runs at build time and inside it we return all the possible values of our route parameters.  Then once we do, Next.js will know to generate a route and a page for each of those parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  How they work together
&lt;/h2&gt;

&lt;p&gt;The Next.js syntax &lt;code&gt;[param]&lt;/code&gt; allows for a page file to have the dynamic route capability based on parameters.  Within this file, you can have the two functions I discussed.  The &lt;code&gt;getStaticPaths&lt;/code&gt; function which will build the paths and pages for each individual details page.  The &lt;code&gt;getStaticProps&lt;/code&gt; function fetches the data related to those individual details pages and adds the data unique to those pages statically.  At a high level, this is how these two functions work together in a dynamic route page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js &amp;amp; WPGraphQL
&lt;/h2&gt;

&lt;p&gt;When you use Next.js and WPGraphQL for Headless WordPress, an issue that you will run into is pre-rendering all your paths and pages in the function called &lt;code&gt;getStaticPaths&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Building ALL pages every time a build is run leads to the WordPress server being hammered and sometimes becoming unresponsive.  Another thing to consider when you do this is the long build times you will have if your site has a lot of pages.&lt;/p&gt;

&lt;p&gt;Here are some symptoms of an unresponsive WP server examples in WPGraphQL: &lt;br&gt;
&lt;code&gt;SyntaxError: Unexpected token &amp;lt; in JSON at position 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6r0m4jyodgxqvl2cc0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6r0m4jyodgxqvl2cc0p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This code block below is a headless WordPress starter that my teammate Jeff made using Next.js.  This is my &lt;code&gt;getStaticPaths&lt;/code&gt; function at the bottom of my  dynamic route file page &lt;code&gt;[slug].js&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticPaths() {
  const GET_POSTS = gql`
    query AllPostsQuery {
      posts(first: 10000) {
        nodes {
          id
          title
          slug
          uri
        }
      }
    }
  `;
  const response = await client.query({
    query: GET_POSTS,
  });

  const posts = response?.data?.posts?.nodes;
  const paths = posts.map(({ slug }) =&amp;gt; {
    return {
      params: {
        slug: slug,
      },
    };
  });

  return {
    paths,
    fallback: false,
  };
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a similar pattern we've seen in a few popular WordPress starters such as Colby &lt;a href="https://github.com/colbyfayock/next-wordpress-starter" rel="noopener noreferrer"&gt;Fayock's&lt;/a&gt; and &lt;a href="https://dev.tourl"&gt;WebDevStudios&lt;/a&gt;.  While this pattern feels intuitive, it can actually be problematic.&lt;/p&gt;

&lt;p&gt;The first thing to notice at the top of this function is my GraphQL query and what it is fetching.  It is fetching 10000 nodes from WPGraphQL.  By default, WPGraphQL prevents more than 100 per request.  If I continue to use this query, it either will only return &lt;a href="https://github.com/wp-graphql/wp-graphql/issues/1566#issuecomment-726459569" rel="noopener noreferrer"&gt;100&lt;/a&gt; items or I will need to make bespoke modifiers in WPGraphQL to support this use case and Jason Bahl who created and maintains WPGraphQL highly advises against &lt;a href="https://github.com/WebDevStudios/nextjs-wordpress-starter/issues/1008#issue-1228084495" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have a variable of paths and I am mapping through posts to grab the  slug that I set it to.  In the return object of the variable I have params giving us the slug of the post. Below that variable, I have a return object with the paths property getting all the paths and if that path does not exist in my prebuilt static paths is a 404 page which is &lt;code&gt;fallback: false&lt;/code&gt; in Next.js.&lt;/p&gt;

&lt;p&gt;When the 10000 nodes are returned, they are passed as paths and Next.js will build every single page and each page has a GraphQL query or more and is sent to the WordPress server, which then overwhelms the server.  This is not optimal as I as I stated since this will not only overwhelm your server and make for a bad user experience on your site, but you will also accrue costs if your site gets larger for tools that charge for build times since your build times will continue to increase.&lt;/p&gt;

&lt;p&gt;This is what it looks like when I run &lt;code&gt;npm run build&lt;/code&gt; to create an optimized production build and build directory of my site within terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvykmiqfc8x7fre1k5vwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvykmiqfc8x7fre1k5vwr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;/posts/[postSlug].js&lt;/code&gt; folder and file.  Due to the way I have my getStaticPaths function set up, you can see that it is pre-building every single path and the time it takes to build them.  Now, imagine if this was a site with hundreds or thousands of pages like &lt;a href="https://www.espn.com/" rel="noopener noreferrer"&gt;ESPN&lt;/a&gt;.  This would not be optimal.  It could take hours to build every page.&lt;/p&gt;

&lt;p&gt;An alternative to consider to fix this issue in your Dynamic Route file within your getStaticProps function in the return statement would be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
export async function getStaticPaths() {
  const paths = [];
  return {
    paths,
    fallback: "blocking",
  };
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same return statement shown previously.  The difference is with setting the paths as an empty array and adding &lt;code&gt;fallback: "blocking"&lt;/code&gt; ; this tells Next.js to not pre-build pages at build time.  This will instead be Server Rendered upon each visit and Statically Generated upon subsequent visits.  Doing this alleviates the issue of unnecessary GraphQL queries sent to the WordPress server and really long build times.&lt;/p&gt;

&lt;h2&gt;
  
  
  nodeByUri Query
&lt;/h2&gt;

&lt;p&gt;One thing to note is the change of your query when you are going to server render your pages.  The initial issue was that the query was asking for 10,000 posts and sent the post through the context of each path being pre-built.  What we need now is a way to get the url out of the context and then query the page based on that using &lt;code&gt;nodeByUri&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is a logic &lt;a href="https://github.com/jasonbahl/headless-wp-template-hierarchy/blob/master/next-apollo/wp-next/template.js#L365-L373" rel="noopener noreferrer"&gt;example&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; export const SEED_QUERY = gql`
query GetNodeByUri($uri: String!) {
    node: nodeByUri(uri: $uri) {
      ...NodeByUri
    }
  }


 if ( context.resolvedUrl ) {
    params = context?.params ?? null;
    resolvedUrl = context?.resolvedUrl ?? null;
    
  } else if ( context?.params?.WordPressNode ) {
    params = context?.params ?? null;
    isStatic = true;
    resolvedUrl = context?.params?.WordPressNode ? context?.params?.WordPressNode.join('/') : null;
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code example is getting the url of the page the user is visiting, then using that in the &lt;code&gt;nodeByUri&lt;/code&gt; query.  This allows users to do fallback: blocking, paths: [] but still have the context needed to grab the data and build the page.  This &lt;a href="https://www.youtube.com/watch?v=ze2un3ni1CY" rel="noopener noreferrer"&gt;video&lt;/a&gt; gives a walk through as well for reference if you need an overview of the query. &lt;/p&gt;

&lt;p&gt;This is what my production build looks like now with this syntax change when I run &lt;code&gt;npm run build&lt;/code&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx001y3lewazjs6nc3p8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx001y3lewazjs6nc3p8.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this image, the &lt;code&gt;/posts/[slug].js&lt;/code&gt; folder and file is not pre-building the paths.  It is allowing paths and pages to be generated on the fly by Server Rendering.  No unnecessary path and page prebuilds.&lt;/p&gt;

&lt;p&gt;If you have really important pages, you could put them in paths like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticPaths() {
    return {
        paths: [
          '/some-really-important-page',
        ],
        fallback: 'blocking'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Next.js to build only the paths specified in the array.  The rest are Server Rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  ISR Option
&lt;/h2&gt;

&lt;p&gt;If you have content editors who want pages to be available close to the time that the content is published in WordPress and not after each new build step is complete, Incremental Static Regeneration or ISR for short is the best option.  Even for cases that have very important pages you want to make sure are always static.&lt;/p&gt;

&lt;p&gt;The code within your &lt;code&gt;getStaticProps&lt;/code&gt; function in your Dynamic Route file to invoke &lt;code&gt;ISR&lt;/code&gt; would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticProps() {
   return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is saying that every 10 seconds, Next.js will will revalidate the data on and this page upon user request.  The caveat here is that the initial user who request this page will get the stale data but every user and request for this page after that initial request will get the fresh data within the timed interval you set. (You can set whatever time you want to revalidate).  If you want a deeper dive into ISR, please reference the &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; docs and our very own Jeff Everhart's blog &lt;a href="https://developers.wpengine.com/blog/crash-course-build-a-simple-headless-wordpress-app-with-next-js-wpgraphql" rel="noopener noreferrer"&gt;post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ISR Considerations
&lt;/h2&gt;

&lt;p&gt;A scenario to consider when you use ISR is a busy site with lots of visits.  Staying with my time stamp example in my code block, I set it to revalidate every 10 seconds.  Imagine that I have a very large, busy site and I invoke ISR on 5,000 pages.  If I get traffic to all those pages and set to revalidate it every 10 seconds, it will rebuild all of the paths and pages every 10 seconds and you are back to square one with the original issue of overwhelming your WordPress server.&lt;/p&gt;

&lt;p&gt;Now, this is just something I want to point out for consideration.  For the most part, ISR is still the best option in our opinion.  You can set your time stamp to an increased time interval as well as figure out how often each type of data really does change and configure it that way to optimize this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-Demand ISR Option
&lt;/h2&gt;

&lt;p&gt;Next.js has a feature called &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#using-on-demand-revalidation" rel="noopener noreferrer"&gt;On-Demand ISR&lt;/a&gt; which is similar to ISR except the difference with this feature is that instead of a time stamp interval and a visit from a user revalidating your stale data, you can update and revalidate the data and content of a page "on-&lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#using-on-demand-revalidation" rel="noopener noreferrer"&gt;demand"&lt;/a&gt; or manually; configuring WordPress to send a webhook to an API route in Next.js when an update to WordPress backend is made.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to throttle Next.js's concurrency
&lt;/h2&gt;

&lt;p&gt;and export phase in relation to how many threads it is using.  Lowering the number of CPU's to reduce concurrent builds will alleviate resources on the server requests when Next.js builds your site.  The object in the next.config.js file at the root of the project for this option is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = uniformNextConfig({
  experimental: {
    // This is experimental but can
    // be enabled to allow parallel threads
    // with nextjs automatic static generation
    workerThreads: false,
    cpus: 1
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an &lt;a href="https://docs.uniform.dev/sitecore/deploy/how-tos/how-to-control-nextjs-threads/" rel="noopener noreferrer"&gt;experimental feature in Next.js&lt;/a&gt;.  In the config file above, the cpus are set to the value of your limits on your WordPress concurrent connections.  This example shows 1.  I do recommend you not setting it to the max since you want to leave some for WordPress editors.&lt;/p&gt;

&lt;p&gt;The trade-off to this approach is it will slow down the build step, while will reducing the number of pages it tries to build simultaneously.  This can help when you WordPress exceeding limitations under the number of requests. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &amp;amp; Future Solutions
&lt;/h2&gt;

&lt;p&gt;After seeing some Headless WordPress setups on Next.js and discussing issues within this topic with the community and WPGraphQL, we believe it is optimal to not pre-render every static path and page within getStaticPaths and a Dynamic Route file in Next.js to reduce running into server and GraphQL issues.&lt;/p&gt;

&lt;p&gt;Adopting Headless WordPress and using Next.js can be daunting, especially if you have are not familiar with the ecosystem, its issues, and best practices to solve those issues.  &lt;/p&gt;

&lt;p&gt;Currently, there is no solution on WP that accurately listens to events and communicates with Next.js.  Don't worry though! Myself, the Headless WordPress team, and WPGraphQL here at WP Engine are working actively to continue to solve these issues in the very near future so stay tuned!!!!&lt;/p&gt;

&lt;p&gt;Hopefully, this blog post on best practice tips of this focused topic was helpful and gave you a better understanding on optimizing Next.js, WPGraphQL, and getStaticPaths!  If you want to see this blog post come to life in a live stream video code tutorial, come join Colby, Jason and myself as we refactor according to these best practices &lt;a href="https://www.youtube.com/watch?v=aALXrN4EmCA" rel="noopener noreferrer"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;As always, hit us up on &lt;a href="https://discord.com/invite/J2khkF9XYK" rel="noopener noreferrer"&gt;discord&lt;/a&gt; if you have any questions, thoughts, or just want to Jamstoke out with us!  &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>graphql</category>
      <category>wordpress</category>
      <category>react</category>
    </item>
    <item>
      <title>Multi-Lingual Headless WordPress</title>
      <dc:creator>Fran Agulto</dc:creator>
      <pubDate>Mon, 04 Jul 2022 20:04:06 +0000</pubDate>
      <link>https://dev.to/franadev/multi-lingual-headless-wordpress-1l71</link>
      <guid>https://dev.to/franadev/multi-lingual-headless-wordpress-1l71</guid>
      <description>&lt;p&gt;In this walk-through tutorial, you will learn how to create a headless WordPress site with multi-lingual functionality using Next.js, Next.js internationalized routing, Polylang, WPGraphQL, and the WPGraphQL Polylang extension. This tutorial assumes a fundamental understanding of Node.js, React/Next.js and its data fetching methods and routing, WPGraphQL, Apollo Client and WordPress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js and npm installed&lt;/li&gt;
&lt;li&gt;Code editor (&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; is what I use)&lt;/li&gt;
&lt;li&gt;A WordPress host with an install already spun up (&lt;a href="//www.wpengne.com"&gt;WP Engine&lt;/a&gt; is my go to) or a free demo environment with &lt;a href="https://localwp.com/connect-to-wp-engine/" rel="noopener noreferrer"&gt;Local&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;a href="//www.github.com"&gt;Github&lt;/a&gt; repository and account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this tutorial, you will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the Polylang Plugin with its WPGraphQL extension&lt;/li&gt;
&lt;li&gt;Understand and use Internationalized routing in Next.js&lt;/li&gt;
&lt;li&gt;Create a home page with multi-lingual posts content &lt;/li&gt;
&lt;li&gt;Create single post detail page with multi-lingual post detail content&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The Polylang WordPress Plug-in
&lt;/h1&gt;

&lt;p&gt;The Polylang WordPress plugin enables multi-lingual site capability for numerous languages for your WordPress content.  This is valuable because this allows you to create content and pages that are localized to your users that speak different languages.  There is a pro version.  For this tutorial, we are going to use the free version.   Let's install the Polylang plugin and quickly cover how it is &lt;br&gt;
traditionally used.&lt;/p&gt;
&lt;h2&gt;
  
  
  Login to your WP Admin and Install Polylang/WPGraphQL extension
&lt;/h2&gt;

&lt;p&gt;Login to your WP Admin.  From &lt;code&gt;Plugins &amp;gt; Add new&lt;/code&gt; menu, search for WPGraphQL, install it then search for Polylang. It will be the first plugin that populates in the WordPress plugin directory. Install and activate the plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf8hasxkujmfb69902dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf8hasxkujmfb69902dn.png" alt="Image description" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Check Pretty Permalink Settings
&lt;/h3&gt;

&lt;p&gt;All of the code in this tutorial is set around having the Post name option selected for your permalink settings. To modify this setting, open the &lt;code&gt;Settings &amp;gt; Permalinks&lt;/code&gt; menu in the WP Admin dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim5arg7whpt3mb7lzbt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim5arg7whpt3mb7lzbt5.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the plugin is activated, you will see a new item on the side menu called &lt;code&gt;Languages&lt;/code&gt;.  Navigate to &lt;code&gt;Languages &amp;gt; Settings &amp;gt; URL&lt;/code&gt; modifications.  Click to the options "The language is set in pretty permalinks, Hide URL language information for default language, Remove /language/ in pretty permalinks."  These three settings allows us to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have our language URL set to the directory of that language instead of a subdomain&lt;/li&gt;
&lt;li&gt;hides the URL for our default language so it is not prepended by the code of that default&lt;/li&gt;
&lt;li&gt;Removes the language in permalink so you don't have the entire string of the language in the link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fok7icz3931ffvtdua9je.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fok7icz3931ffvtdua9je.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Install Polylang WPGraphQL Extension
&lt;/h2&gt;

&lt;p&gt;Next, we need to expose Polylang's languages and translations for our post in the GraphQL schema. Go to the WPGraphQL for Polylang github repository page here: &lt;a href="https://github.com/valu-digital/wp-graphql-polylang" rel="noopener noreferrer"&gt;https://github.com/valu-digital/wp-graphql-polylang&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you do that, click on the green code button and go to the bottom of the dropdown and download the zip file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjjo1mbdiicklp0apg2zj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjjo1mbdiicklp0apg2zj.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now go back to your WP admin. From the Plugins &amp;gt; Add new menu, go to the upload plugin button at the top. Click that and it will bring up a &lt;code&gt;choose file&lt;/code&gt; box. Click on that and choose the zip file you downloaded from the WPGraphQL Polylang repository. You can now activate and install it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz26fndax61knvl807eip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz26fndax61knvl807eip.png" alt="Image description" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Posts/Post Polylang Language Feature
&lt;/h3&gt;

&lt;p&gt;The next step is to start populating some data in English and Spanish.  Navigate to Post in the menu.  When you get into the Post edit page, you will now see an American and Mexican flag.  This is the Polylang plugin at work.  It ties a single post type together with its translations you add to it. In this case English and Spanish.  I am a bit of a Star Wars nerd, hence the Star Wars content but you can use any content you like. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhidmnm8ph2ia1pcjd1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhidmnm8ph2ia1pcjd1r.png" alt="Image description" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you click into the editor interface, you can see on the right side menu an option for &lt;code&gt;Languages&lt;/code&gt;.  When you click on that then click on the pencil icon, it lets you toggle between your English and Spanish versions. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5urh00mze7dy9aormai0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5urh00mze7dy9aormai0.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooweoygpqcnbvwja8rv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooweoygpqcnbvwja8rv0.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is how you setup the polylang plugin for WordPress in a traditional approach.  When you visit your site, you will have the translator of your browser appear and you can toggle back and forth between English and Spanish.  You can use whatever translator you want but bing translator is my favorite and most reliable&lt;/p&gt;
&lt;h2&gt;
  
  
  Constructing our GraphQL queries for translated posts
&lt;/h2&gt;

&lt;p&gt;Now that we have our posts data and extended the WordPress and Polylang data schema with a GraphQL layer using WPGraphQL and its extension, let's build our queries to grab that multi-lingual post data from WordPress. &lt;/p&gt;

&lt;p&gt;In your WP Admin, go to the GraphiQL IDE and using the query composer, create this query shown here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4j9kgduck7j6gq4aipgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4j9kgduck7j6gq4aipgq.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; query posts($language: LanguageCodeFilterEnum!)  {
      posts(where: { language: $language }) {
        edges {
          node {
            id
            excerpt
            title
            slug
            language {
              code
              locale
            }
          }
        }
      }
      generalSettings {
        title
        description
      }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Polylang plugin and &lt;code&gt;WPGraphQL&lt;/code&gt; extension is allowing the &lt;code&gt;$language&lt;/code&gt; to be the variable and requiring the &lt;code&gt;LanguageCodeFilterEnum!&lt;/code&gt; to be the required value. When you query the language variable as an &lt;code&gt;object&lt;/code&gt; in your composer and hit the play (➡️) it will give you all the posts in that language. Change it to &lt;code&gt;"ES"&lt;/code&gt; to get Spanish translations back. Stoked!!🥳&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqnr6tnrxsf6wwdi3k78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqnr6tnrxsf6wwdi3k78.png" alt="Image description" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Next JS Headless WP Starter cloned and running locally
&lt;/h2&gt;

&lt;p&gt;Since this is not a ground up tutorial on React and Next.js fundamentals, I have a demo site built for you already that utilizes  Next.js and its data fetching capabilities with SSG, Apollo Client, Next.js Internationalized routing and Dynamic Routing. Go to your terminal and clone down the repository with this shell command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone git@github.com:Fran-A-Dev/nextjs-&lt;br&gt;
polylangwpgql.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After cloning, run npm install and open the files.  The file structure in my Next.js starter has a components folder pages folder, lib folder, public folder, and a styles folder.  I have scaffolded this starter to include static site generation data fetching method, the Apollo Client instance, dynamic post page routing and Next.js internationalized routing.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Next.js Internationalized Routing
&lt;/h3&gt;

&lt;p&gt;Now that you have the starter site cloned, let's break down things on the front end starting with internationalized routing on Next.js.  When you are doing Headless WordPress and have users visiting your site that speak different languages, internationalizing your content is crucial to having a great user experience. &lt;/p&gt;

&lt;p&gt;We started this tutorial learning how to internationalize our content in WordPress traditionally and decoupling with WPGraphQL.  In this next section, we will put it all together by connecting it with our front end and explore its internationalization feature. There are many different approaches to doing internationalization with Next.js. In our tutorial we are going to use router and link to switch translated content from WordPress.  &lt;/p&gt;

&lt;p&gt;Next.js makes it seamless and easy to set up internationalization through a configuration object with the property i18n and within that object, you define your key which is locale with the default locale you want and its translations.  Locale is the syntax used to define language and is a UTS identifier for standardized format for defining locations and language.  In this case, English is the default and Spanish is the translated.  Navigate to next.config.js file in the root of the project to see the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  reactStrictMode: true,
  i18n: {
    locales: ["en", "es"],
    defaultLocale: "en",
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring this allows Next.js to know what available locales we have in our app and each page when it gets rendered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to filter between languages
&lt;/h3&gt;

&lt;p&gt;need to set up a link we can click on to filter between the languages on posts.  Go to &lt;code&gt;components/Navbar.js&lt;/code&gt; in the project.  You should see this code within the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Link from "next/link";
import { useRouter } from "next/router";
import styles from "../components/Navbar.module.css"

export default function Navbar() {
  const { locale: activeLocale, locales, asPath } = useRouter();

  const availableLocales = locales.filter((locale) =&amp;gt; locale !== activeLocale);

  return (
    &amp;lt;div className="container"&amp;gt;
      &amp;lt;nav&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;Link href="/"&amp;gt;
              &amp;lt;a className={styles.home}&amp;gt;Home&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;

        &amp;lt;/ul&amp;gt;
        &amp;lt;ul&amp;gt;
          {availableLocales.map((locale) =&amp;gt; {
            return (
              &amp;lt;li key={locale}&amp;gt;
                &amp;lt;Link href={asPath} locale={locale}&amp;gt;
                  &amp;lt;a className={styles.toggle}&amp;gt;{locale.toUpperCase()}&amp;lt;/a&amp;gt;
                &amp;lt;/Link&amp;gt;
              &amp;lt;/li&amp;gt;
            );
          })}
        &amp;lt;/ul&amp;gt;
      &amp;lt;/nav&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this file down and explain what Next.js is doing here.  First,  we need to program a way to list all the locales available, which locale the user is currently active on and the actual path of that locale.  I am doing this by using the destructured &lt;code&gt;useRouter&lt;/code&gt; hook with &lt;code&gt;next/router&lt;/code&gt; imported at the top of the file with this line: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;import { useRouter } from "next/router";&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;top of the component and destructure to extract all the locale constant variables that are supported in my application:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const { locale: activeLocale, locales, asPath } = useRouter();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The locale variables seen above are all my locales, the actual active locale that the user will be on when viewing the page and the path that reflects the locale that they are on. The next step is to only show the user the locales that are not active so that the user can know what locale they can link to besides their current default on the page. &lt;/p&gt;

&lt;p&gt;To accomplish this, I created a constant and called it availableLocales.  I set it equal to locales and I filter through that array of locales.  Filtering through that array of locales, we return only the locales that are not equal the active one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const availableLocales = locales.filter((locale) =&amp;gt; locale !== activeLocale);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now that is done, I need to pass this into my return statement in my list of links to make it an actual Link with next/link to display a clickable link which will act as our toggle between our two different locales with the correct path as shown in these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;li&amp;gt;
            &amp;lt;Link href="/"&amp;gt;
              &amp;lt;a className={styles.home}&amp;gt;Home&amp;lt;/a&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;

        &amp;lt;/ul&amp;gt;
        &amp;lt;ul&amp;gt;
          {availableLocales.map((locale) =&amp;gt; {
            return (
              &amp;lt;li key={locale}&amp;gt;
                &amp;lt;Link href={asPath} locale={locale}&amp;gt;
                  &amp;lt;a className={styles.toggle}&amp;gt;{locale.toUpperCase()}&amp;lt;/a&amp;gt;
                &amp;lt;/Link&amp;gt;
              &amp;lt;/li&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down more granularly, in the &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; I take my available locales constant and map through it returning the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; list item, and inside I replace the value within the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag with locale as the value and make the letters upper case. &lt;/p&gt;

&lt;p&gt;What is happening here is I added a new prop of locale and I set it equal to locale to change the locale from English to Spanish and vise versa.  Then, we have to pass the link it should navigate to in relation to where the user is currently located.  So we use the asPath variable from our useRouter hook that we destructured and in the href we set it equal to asPath.  Now that this is set, the user can click on the link in the Navbar to toggle back on forth for the proper language and its path.&lt;/p&gt;

&lt;p&gt;Once I finished this &lt;code&gt;Navbar.js&lt;/code&gt; file, I wrapped the component in the &lt;code&gt;pages &amp;gt;_app.js&lt;/code&gt; file so my entire app has access to it across the pages. &lt;/p&gt;

&lt;h3&gt;
  
  
  Apollo Client/GraphQL setup
&lt;/h3&gt;

&lt;p&gt;This project does use apollo and GraphQL.  In this demo, I chose to just create a lib folder with a apollo-client.js file to make an apollo instance.  I also did not use .env.local which is a Next.js convention to not show environment variables and allow Next to detect those.  In this demo's case, I am directly putting that endpoint into the function&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f1mpjzxbn5vp8v213z9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f1mpjzxbn5vp8v213z9.png" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding our GraphQL Queries
&lt;/h3&gt;

&lt;p&gt;In the previous section, we enabled Next.js internationalization functionality and added a Link in our Navbar that allows users to toggle the available locales or languages on the site. Lastly, we need to add the queries we made with WPGraphQL in our WordPress admin to our front end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Lingual Posts on the Home Page
&lt;/h2&gt;

&lt;p&gt;Remember our GraphQL query we made with the language variable at the beginning of this walk-through tutorial?  Go to the &lt;code&gt;pages/index.js/&lt;/code&gt; file and here we see it at the bottom being queried within our &lt;code&gt;getStaticProps&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticProps({ locale }) {
  const apolloClient = getApolloClient();

  const language = locale.toUpperCase();

  const data = await apolloClient.query({
    query: gql`
    query posts($language: LanguageCodeFilterEnum!)  {
      posts(where: { language: $language }) {
        edges {
          node {
            id
            excerpt
            title
            slug
            language {
              code
              locale
            }
          }
        }
      }
      generalSettings {
        title
        description
      }
    }
    `,
    variables: {
      language,
    },
  });




&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see with the above code snippet that we are querying for all posts and their different languages tied to their default English setting.  We are using Apollo client for the client side help to fetch this data along with setting the constant of &lt;code&gt;const language = locale.toUpperCase();&lt;/code&gt;to provide and call the filter we have that we made within the Navbar.&lt;/p&gt;

&lt;h3&gt;
  
  
  WPGraphQL/Polylang Single Post query
&lt;/h3&gt;

&lt;p&gt;We have our project set up now where we have our home page displaying all of our posts with the ability to click the link at the top of the navbar and switch paths between English and Spanish.&lt;/p&gt;

&lt;p&gt;The next thing we need to do is create a query in our GraphiQL IDE back in the WP Admin to get a single post and its translation.  Heading back into our query composer, create a query that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffn6h2f6zxe59wp6kiywb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffn6h2f6zxe59wp6kiywb.png" alt="Image description" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This query is asking for the single post type with the variable being the slug, with the fields we want to  grab along with the available translation and the language being the variable.  When you query your variables via slug and language and press play in the GraphiQL IDE, you will get back the single post you want by its slug and its available translated version. &lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Route Page and Single Post Query
&lt;/h3&gt;

&lt;p&gt;in our Next.js front end and setting up the query we just made for the single post to show up.  This will allow users to click on the post they want to see on the home page, route them to that single post's detail page dynamically.  When they are on the post detail page, users can continue to be able to filter between English and Spanish.&lt;/p&gt;

&lt;p&gt;Navigate back to the Next.js project and go to &lt;code&gt;pages/posts/ [postsSlug].js.&lt;/code&gt; At the bottom of the file within &lt;code&gt;getStaticProps&lt;/code&gt;, you will see our query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticProps({params, locale}) {
const { postSlug } = params;
const language = locale.toUpperCase();

const apolloClient = getApolloClient();

  const data = await apolloClient.query({
    query: gql`
    query PostBySlug($slug: String!, $language: LanguageCodeEnum!) {
      generalSettings {
        title
      }
      postBy(slug: $slug) {
        id
        content
        title
        slug
        translation(language: $language) {
          id
          slug
          content
          title
          language {
            locale
            slug
          }
        }
      }
    } 



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is similar to our Home page query.  The difference is we now are  asking for a single post with our dynamic routes page and path to the that particular post detail page.  When displayed, we will see our specific fields to that single post we asked for with the ability to toggle back and forth on its language translations.&lt;/p&gt;

&lt;p&gt;Next, let's focus on our &lt;code&gt;getStaticPaths&lt;/code&gt; function, we can statically pre-build the static paths with our pre-built pages on our app since we have dynamic routes for the details of each page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticPaths({ locales }) {
  const apolloClient = getApolloClient();

  const data = await apolloClient.query({
    query: gql`
      {
        posts(first: 10000) {
          edges {
            node {
              id
              title
              slug
            }
          }
        }
      }
    `,
  }); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I destructured the context of locales so I can have all the locales variables.&lt;/p&gt;

&lt;p&gt;Now we are creating all the paths for only the existing default routes here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const paths = posts.map(({ slug }) =&amp;gt; {
    return {
      params: {
        postSlug: slug,
      },
    };
  });

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we need to do is have a way to add on the locales for our existing paths.  This is done here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


 return {
    paths: [...paths,
      ...paths.flatMap((path) =&amp;gt; {
        return locales.map((locale) =&amp;gt; {
          return {
            ...path,
            locale,
          };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the return statement, I have an array open and I am spreading out the paths that already exist which are default routes.  The trick here is to spread out another instance of paths and then I use the flatMap method to go deeper into another level of array with the locales.  The &lt;code&gt;flatMap&lt;/code&gt; method is like the map method but it allows you to flatten out arrays that have a top level array, in this case the default paths and flatten them out into the top level array. &lt;/p&gt;

&lt;p&gt;Then for each path, it returns a new map statement mapping through the locales and returning the path and the locale together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the demo locally
&lt;/h2&gt;

&lt;p&gt;We are almost done.  All you have to do now is go to your terminal, run &lt;code&gt;npm run dev&lt;/code&gt; and get your browser up on &lt;code&gt;localhost:3000.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see this on your browser with the Navbar showing the available locale and language not in use which is Spanish:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhamw5xuchy2eazgb61q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhamw5xuchy2eazgb61q.png" alt="Image description" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on the link and it should translate and you should see the same posts displayed with their Spanish translations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuof21nbu033fqwp656eh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuof21nbu033fqwp656eh.png" alt="Image description" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on one of the Spanish post displayed and it will take you to the details page of that post and its path. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci0ox5nrb93z5p7vopw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci0ox5nrb93z5p7vopw0.png" alt="Image description" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, click again on the link atop the EN link at the top of the Navbar to switch this post detail page to its default English version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fet7lwgrob5lv01285fx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fet7lwgrob5lv01285fx3.png" alt="Image description" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Done!! Stoked! 🎉
&lt;/h2&gt;

&lt;p&gt;We are finished with this walk through tutorial!  Jamstoke!  With this tutorial, we hope you take away the importance of adding multi-language capability on your sites and apps, leveraging tools like WPGraphQL and all its extensions like Polylang, Next.js internationalized routing as well as Next.js fundamental features.&lt;/p&gt;

&lt;p&gt;This is just one way of approaching multi-language Headless WordPress.  I would love to hear your thoughts and your own approaches and builds.  Hit me up in our &lt;a href="https://discord.com/invite/J2khkF9XYK" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Here is the demo once again so you can follow along, make it your own and add on to it if you like on my &lt;a href="https://github.com/Fran-A-Dev/nextjs-polylangwpgql" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;a href="https://hiadtkkmekv2zn3q0zt8yq845.js.wpenginepowered.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want to see the site live! If you are looking for headless WordPress hosting, my site is on the &lt;a href="https://wpengine.com/atlas/" rel="noopener noreferrer"&gt;Atlas platform&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>graphql</category>
      <category>wordpress</category>
      <category>jamstack</category>
    </item>
  </channel>
</rss>
