Skip to main content

Cross-post navigation with Next.js and GraphCMS

How to add links to next and previous posts


One feature I prefer on a blog post page is a link to the next and previous post. I couldn't find any ready-made solution for Next.js and my CMS (GraphCMS), so I implemented my own. Even though my solution is for GraphCMS, the logic should work for any CMS.

Defining next and previous

In my case, a previous post is a post that is the first post published before the current post. But this is not the only option. These would also be valid criteria:

  • Show the next and previous posts based on the date the post is updated
  • Add the next and previous posts manually to your CMS as separate fields

Pick the approach that suits your needs the best.

First steps

As getting next and previous links share the same logic, let's simplify our approach and create a link only to the next post. Here's what we need to achieve:

  1. Get the date of the current post
  2. Get the first post after that date
  3. Get the slug and title of that post

First, we build a simple function to fetch the next post. In Next.js this might happen in a [slug].tsx file within a getStaticProps function. This is a redacted example but should convey the idea. I've also numbered the above steps in the code sample.

export const getStaticProps: GetStaticProps = async (context) => {
  const post = () => {
    // Get current post from your CMS or filesystem
  }
  const nextPost = async () => {
    // Fetch data from a CMS (GraphCMS in this case)
    return await fetchFromGraphCMS({
      query: `
        query ($date: Date!) {
          # 2. Get the post where the date is greater than the date we pass in
          posts(where: { date_lt: $date }, first: 1, orderBy: date_DESC) {
            # 3. Get whatever data you'd like to display in your link.
            slug
            title
          }
        }
      `,
      variables: {
        // 1. Use the date from your post page
        date: post.date,
      },
      preview: context.preview,
    })
  }
  return {
    props: {
      data: post,
      // The nextPost.posts is an array with only one object
      nextPost: nextPost.posts[0]
    },
  }
}

Next, we use the returned nextPost object in our layout.

import Link from 'next/link'

const SinglePostPage = ({ data, nextPost }) => {
  // Destructure title and slug from nextPost
  const { title, slug } = nextPost || {}

  return (
    <Layout>
      {/* ...other content... */}
      <Link href={slug}>
        <a>{title}</a>
      </Link>
    </Layout>
  )
}

And that's it! Now you should have a working link to your next post. The same logic works for the previous link.

I eventually built a generic solution to handle both next and previous links. As I'm using the same functionality on other pages in addition to posts, I extended the solution to work with other pieces of content too.

// getRelatedContent.tsx
import { fetchFromGraphCMS } from '../lib/graphcms'
import { RelatedContentType } from '../lib/types'
import { getLink } from './getLink'

export const getRelatedContent = async ({
  date,
  content,
  kind,
  preview,
}: RelatedContentType) => {
  // Determine whether to fetch a post with date less or greater than the date
  // we pass in.
  const dateType = kind === 'previous' ? 'date_lt' : 'date_gt'
  // Determine the sort order based on the direction we're going (to the past
  // or to the future).
  const sortOrder = kind === 'previous' ? 'date_DESC' : 'date_ASC'
  const data = await fetchFromGraphCMS({
    query: `
      # Define our date variable
      query ($date: Date!) {
        # We look for the first piece of content with the date less or greater
        # than the date we pass in.
        ${content}(where: { ${dateType}: $date }, first: 1, orderBy: ${sortOrder}) {
          # Get whatever data you'd like to display in your link.
          slug
          prefix
          title
        }
      }
    `,
    variables: {
      // Pass in the date to the query
      date,
    },
    // Determine whether to take draft content into account
    preview,
  })

  if (!data[content][0]) {
    return {}
  }

  const { slug, prefix, title } = data[content][0]

  return {
    href: getLink({ href: slug, prefix }),
    title,
  }
}

Now go and play around with your brand new cross navigation links.

Get in touch

I'm not currenlty looking for freelancer work, but if you want to have a chat, feel free to contact me.

Contact