Join us live as we unveil the all new Hygraph Studio!

Hygraph FM: Adding pagination with Next.js and maintaining SEO

Let’s take our website a step further by adding pagination to our code that will dynamically generate sequential pages of artists that users can click through to see all available artists.
Lo Etheridge

Lo Etheridge

Nov 29, 2023
Hygraph FM Adding pagination and maintaining SEO

Previously, we set up a frontend for HygraphFM using Next.js 13. Normally, music platforms have thousands of artists available to stream. Having all of these artists displayed on a single page will clutter and overwhelm the user experience.

Editor's Note

Missed the first part of the series? Clone the schema and content to follow along with this article.

Series:

#Adding pagination to Hygraph FM

Pagination refers to the process of dividing content into separate pages to improve user experience and optimize performance. It plays a crucial role in managing large datasets or long lists of items, allowing users to navigate through the content more efficiently.

We need to help our platform scale to accommodate more artists while maintaining a good UI for our listeners—enter Pagination! Let’s take our website a step further by adding pagination to our code that will dynamically generate sequential pages of artists that users can click through to see all available artists.

Pagination can take several forms such as the ability to click on sequential individual pages that are numbered along with next and previous buttons, a load more button, and infinite scrolling with lazy-load images. We will use server-side sequential pagination for our HygraphFM project.

Modifying our Artists page.jsx route

Change the Artists dir to add an optional catch-all segment to the route like so:

/* File tree with artists
index page template */
app/
┣ artist/
┃ ┗ [slug]/
┃ ┗ page.jsx
┣ artists/[[...pageNumber]]
┃ ┗ page.jsx

What are optional catch-all segments?

Optional catch-all segments allow you to create routes that can match one or more segments of a URL path, where the segments are optional. This is useful when you want to create a route that can match a variable number of segments.

In our case, we are using [[...pageNumber]] as an optional catch-all segment, so that our /artists route without the pageNumber parameter is also matched along with our /artists/pageNumber parameter.

To implement pagination in our Next app, we need to modify our getAllArtists() function that contains our GraphQL query that retrieves our artist data from Hygraph. Update your code with the following additions:

// Location: app/artists/[[...pageNumber]]/page.jsx
// Get all artists query function
async function getAllArtists(pageNumber) {
const response = await fetch(process.env.HYGRAPH_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query MyQuery {
artistsConnection(first: 3, skip: ${(pageNumber - 1) * 3}, orderBy: artist_ASC) {
pageInfo {
hasNextPage
hasPreviousPage
pageSize
startCursor
endCursor
}
aggregate {
count
}
edges {
node {
slug
artist
id
artistImage {
altText
url
height
width
}
}
}
}
}
`,
}),
});
const json = await response.json();
return json.data.artistsConnection;
}

Our async getAllArtists function now takes a pageNumber as an argument and is used to calculate the number of items to skip in the data to implement pagination.

Hygraph follows the Relay cursor connection specification. Each of our project models contain a connection type, automatically managed by Hygraph. Notice that our GraphQL query now contains a connection type query for our Artists model. This allows us access to the pageInfo type so that we can use the following Hygraph system fields:

  • hasNextPage
  • hasPreviousPage
  • pageSize
  • startCursor
  • endCursor

Along with the above system fields, we can also use our connection type query to get an aggregate count and retrieve the total number of artists in HygraphFM. With very little effort, we now have all the data we need to create pagination, so that we can render all of the artists on HygraphFM in a way that is easier to navigate for our users. Next, let’s add in the logic to help generate our sequential pages of artists.

Creating sequential page logic

After we return our JSON data from the above query, we need to add in logic that will calculate the number of pages of artists needed based on the total number of artists returned. Replace the the current Artists async function with the following code:

// Location: app/artists/[[...pageNumber]]/page.jsx
export default async function Artists({ params }) {
const { pageNumber = 1 } = params;
const { edges, pageInfo, aggregate } = await getAllArtists(pageNumber);
const artists = edges.map((edge) => edge.node);
const {pageSize, hasNextPage, hasPreviousPage} = pageInfo;
const {count} = aggregate;
// get total number of pages
const pageTotal = Math.ceil(count / pageSize);
// convert pageTotal to an array with page numbers (page number is array iterator + 1)
const pageArray = Array.from(Array(pageTotal).keys()).map(i => i+1)

Let’s breakdown what is happening in this code! We have modified our Artists async function to take params as an argument with a pageNumber property. We are call our getAllArtists function to fetch a list of all artists from the Hygraph API. The pageNumber argument is used to fetch the correct page of artists for our pagination implementation. After we get a response from our getAllArtists function, it is destructured into edgespageInfo, and aggregate. The edges property is an array of nodes, where each node represents an artist in HygraphFM and they are wrapped into a new array of artists . The pageInfo property contains information about the current page, such as the page size and whether there are next or previous pages, and the aggregate property contains the total count of artists.

Next, our function calculates the total number of pages, pageTotal, by dividing the count of artists by the page size and rounding up to the nearest whole number using the Math.ceil function.

Last, our function creates an array of page numbers, pageArray, by creating an array of the correct length, mapping each item to its index plus one to render our pagination controls.

Now that we have our pagination controls, let’s update our return statement to include our new pagination elements. Modify the return statement to include an unordered list element that contains our pagination controls right after that last closing div :

// Location: app/artists/[[...pageNumber]]/page.jsx
<ul className="flex items-center justify-center py-4 font-bold list-style-none">
{hasPreviousPage && (
<li>
<Link
className="relative block rounded bg-transparent px-3 py-1.5 text-md text-neutral-600 transition-all duration-300 hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-700 dark:hover:text-white"
href={`/artists/${Number(pageNumber) - 1}`}
>
< Previous
</Link>
</li>
)}
{pageArray.map((page) => {
return (
<li key={page}>
<Link
className={`relative block rounded bg-transparent px-3 py-1.5 text-md transition-all duration-300 hover:bg-neutral-100 hover:text-neutral-800
${Number(pageNumber) === page
? "text-white bg-neutral-600"
: "text-neutral-600 dark:text-white dark:hover:bg-neutral-700 dark:hover:text-white"
}`}
href={`/artists/${page}`}
>
{page}
</Link>
</li>
);
})}
{hasNextPage && (
<li>
<Link
className="relative block rounded bg-transparent px-3 py-1.5 text-md text-neutral-600 transition-all duration-300 hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-700 dark:hover:text-white"
href={`/artists/${Number(pageNumber) + 1}`}
>
Next
</Link>
</li>
)}
</ul>

The above code contains our new pagination controls that include, Next, Previous, and page number links with styling using TailwindCSS. The first link checks if there is a previous page, hasPreviousPage , and renders "Previous" link that navigates to the previous page using the  href attribute of the Next  Link component to dynamically set the path of the previous page. The second link item maps over pageArray to render a list item for each page number that contains a link to the corresponding page of artists. The last link item follows the pattern of the previous link, but uses hasNextPage to check if a page has another page after it. If so, a “Next” link is rendered and navigates to the next page. That’s it! If all goes well, your website should contain Next, Previous, and page links.

Screenshot 2023-11-16 at 12.06.54.png

Screenshot 2023-11-16 at 12.07.09.png

Screenshot 2023-11-16 at 12.08.21.png

#Why is pagination Important?

Pagination helps to improve loading times and overall performance. Instead of loading all the content at once, pagination loads content on-demand, reducing the initial load time and minimizing the strain on server resources. This is particularly beneficial for mobile users or those with slower internet connections. Users can easily jump to a specific page or go forward and backward through the pages, saving time and effort in searching or scrolling through long lists.

#Quick word about pagination and SEO

Now that we have added pagination controls to our HygraphFM platform, let’s chat about pagination and SEO. Pagination is fantastic for User experience and accessibility, but here are a few things to keep in mind for maintaining SEO and making sure Google can crawl and index your paginated content:

  1. Make sure to link pages sequentially - We accomplished this in our HygraphFM by building dynamic paths to current, next, and previous pages using the href attribute of our Next Link component. This helps to ensure that search engines understand the relationship between all of our paginated content.
  2. Give each page a unique, canonical URL - Our new pagination features dynamically create routes for our paginated content. Each page of artists has a url in the following pattern: /artists/pageNumber . For example on localhost, the first page of artists has the URL: https://localhost:3000/artists/1 . Creating unique URLs as we have done in this tutorial is the preferred method.
  3. Avoid indexing URLs that have filters or sort orders - Although our contained an orderBy filter so that we could fetch our artists in ASC alphabetical order, we did not include this in our paginated content URLS. Should you want to include sort order, it is best practice to include the noindex robot meta tag to avoid indexing variations of the same content that could rank your content lower in search due to duplicate content.

#Conclusion

Adding pagination enhances the usability of a website or application by breaking down content into manageable chunks. This prevents overwhelming the user with a single, lengthy page and provides a clear structure for accessing information. However, it is important to make sure that pagination does not interfere with SEO and the indexing of your content.

Next steps…

For the final part of the HygraphFM series, we will implement search functionality to further improve the user experience. If you have any questions or feedback, find me in the Hygraph community!

Join the community

Need help? Want to show off? Join the Slack community and meet other developers building with Hygraph

Meet us in Slack

Blog Author

Lo Etheridge

Lo Etheridge

Senior Developer Relations

Lo is Hygraph's Senior Developer Relations Specialist. They focus on developer education around Headless CMS, JS-based frontends, and API technologies as well as the dynamic cross-functional relationship between developers and content professionals.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.