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
Series:
- Part 1: Hygraph FM: Building the artist content model
- Part 2: Hygraph FM: Building an artist page with components and Next.js
#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 artistsindex page template */app/┣ artist/┃ ┗ [slug]/┃ ┗ page.jsx┣ artists/[[...pageNumber]]┃ ┗ page.jsx
What are optional catch-all 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 functionasync 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 {hasNextPagehasPreviousPagepageSizestartCursorendCursor}aggregate {count}edges {node {slugartistidartistImage {altTexturlheightwidth}}}}}`,}),});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.jsxexport 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 pagesconst 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 edges
, pageInfo
, 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.
#Building next, previous, and page number links
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><LinkclassName="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}><LinkclassName={`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><LinkclassName="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.
#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:
- 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.
- 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. - 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 thenoindex
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