❗ 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">←</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">→</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.