Using the previousPost and nextPost Fields

❗ This is not a tutorial ❗

Yesterday I made a WPGraphQL Adjacent Posts plugin. It adds the following fields the the WPGraphQL schema’s Post type:

  • previousPost: Fetches the previous post.
  • nextPost: Fetches the next post.
  • previousPostInCategory: Fetches the previous post within the same category.
  • nextPostInCategory: Fetches the next post within the same category.

The code is here: https://github.com/scossar/wp-graphql-adjacent-posts.

I’m going to use those fields to add previous and next post navigation to my Remix app’s single post pages.

Update GraphQL schema?

If things go as expected, running npm run generate should update the GraphQL schema that’s at app/graphql/__generated__/graphql.ts. Things might not work as I expect though. I’ll try this on a new branch:

$ git checkout -b generate_schema

$ npm run generate

That seems to have worked:

// app/graphql/__generated__/graphql.ts

...
  /** Next post */
  nextPost?: Maybe<Post>;
  /** Next post in same category */
  nextPostInCategory?: Maybe<Post>;
...
  /** Previous post */
  previousPost?: Maybe<Post>;
  /** Previous post in same category */
  previousPostInCategory?: Maybe<Post>;
...

I’ll merge it back into the main branch.

Update POST_BY_SLUG_QUERY

It needs the previousPost and nextPost title and slug fields added:

// app/models/wp_queries.ts

...
export const POST_BY_SLUG_QUERY = gql(`
query GetPostBySlug ($id: ID!) {
  post(id: $id, idType: SLUG) {
    id
    title
    content
    excerpt
    slug
    date
    author {
      node {
        name
      }
    }
    featuredImage {
      node {
        altText
        description
        caption
        id
        sourceUrl
      }
    }
    previousPost {
      title
      slug
    }
    nextPost {
      title
      slug
    }
  }
}
`);
...

Use the new data to create navigation links

Since previousPost and nextPost are properties of the Post object, and the blog.$slug.tsx loader function is returning a Post object, the loader function can stay as it is.

I’m going to make a change to its error handling though. I don’t want to catch the case of a post not being found in the condition that’s handling response errors:

// app/routes/blog.$slug.tsx

...
export const loader = async ({ params }: LoaderFunctionArgs) => {
  invariant(params.slug, "params.slug is required");
  const slug = params.slug;
  const client = createApolloClient();
  const response = await client.query({
    query: POST_BY_SLUG_QUERY,
    variables: {
      id: slug,
    },
  });

  if (response.errors) {
    throw new Error(`An error was returned when querying for slug:\n${slug}`);
  }

  const post = response?.data?.post ?? null;

  if (!post) {
    // todo: this should be handled gracefully
    throw new Error(`No post was returned for slug: \n${slug}`);
  }

  return post;
};
...

The new data is available in the route’s component:

// app/routes/blog.$slug.tsx

...
export default function BlogPost() {
...
  const previousTitle = post?.previousPost?.title;
  const previousSlug = post?.previousPost?.slug;
  const nextTitle = post?.nextPost?.title;
  const nextSlug = post?.nextPost?.slug;
...

The data has the same variable names as I used when trying to implement cursor based page navigation. I can just copy that code into the component and consider this done (for now.)

// app/routes/blog.$slug.tsx

...
      <div className="my-3 grid grid-cols-1 min-[431px]:grid-cols-2 gap-4 items-center h-full">
        {previousTitle && previousSlug ? (
          <div>
            <div>
              <span className="text-5xl">&larr;</span>
              <span>Previous</span>
            </div>
            <Link
              prefetch="intent"
              to={`/blog/${previousSlug}`}
              className="text-lg font-bold text-sky-700 hover:underline"
            >
              {previousTitle}
            </Link>
          </div>
        ) : null}
        {nextTitle && nextSlug ? (
          <div className="min-[431px]:text-right">
            <div>
              <span>Next</span>
              <span className="text-5xl">&rarr;</span>
            </div>
            <Link
              prefetch="intent"
              to={`/blog/${nextSlug}`}
              className="text-lg font-bold text-sky-700 hover:underline"
            >
              {nextTitle}
            </Link>
          </div>
        ) : null}
      </div>
    </div>
  );
}
...

This is good enough for today. I don’t want to get too caught up in styling issues.

One thing to note is that I’m unsure about using <Link prefetch="intent". It makes pages load quickly, but could potentially causing people to download data for posts they’re not interested in, and put a load on my WordPress server? There’s no risk any time soon. The site isn’t even being indexed by Google.