Create Reusable Pagination System in Gatsby with GraphQL
Add to your RSS feed16 September 20249 min readTable of Contents
Creating a reusable pagination system in Gatsby using GraphQL without relying on external plugins involves leveraging Gatsby's built-in createPages API and writing reusable pagination logic. Here’s a step-by-step guide to building a simple yet scalable pagination system that can be reused across multiple content types such as posts, categories, and tags.
1. Setup Your Gatsby Website
Before creating a Gatsby website, you need to install the Gatsby CLI (Command Line Interface) globally on your machine.
Install Gatsby CLI
npm install -g gatsby-cliThis command installs the Gatsby CLI globally on your machine so that you can run Gatsby commands from anywhere.
Create a New Gatsby Project
After installing the CLI, you can create a new Gatsby project using one of Gatsby’s starter templates.
Run the following command in your terminal to create a new project:
gatsby newCheck for the tailwind and MDX
1 What would you like to call your site?2 √ · gatsby-pagionation3 What would you like to name the folder where your site will be created?4 √ NextJS/ gatsby-pagionation5 √ Will you be using JavaScript or TypeScript?6 · TypeScript7 √ Will you be using a CMS?8 · No (or I'll add it later)9 √ Would you like to install a styling system?10 · Tailwind CSS11 √ Would you like to install additional features with other plugins?121314 Thanks! Here's what we'll now do:1516 Create a new Gatsby site in the folder gatsby-pagionation17 Get you set up to use Tailwind CSS for styling your site18 Install gatsby-plugin-mdx192021 √ Shall we do this? (Y/n) · Yes
This will create a new Gatsby site in a directory called gatsby-pagination.
cd gatsby-pagionation2. Add MDX content
Add some mdx content to your content directory.
3. Setup ShadcnUI
Update the tsconfig.json file to integrate with Shadcn. Open tsconfig.json and configure it as follows:
1 {2 "compilerOptions": {3 // ...4 "baseUrl": ".",5 "paths": {6 "@/*": ["./src/*"]7 }8 // ...9 }10 }
Update/create gatsby-node.ts file
1 import * as path from 'path';23 export const onCreateWebpackConfig = ({ actions }) => {4 actions.setWebpackConfig({5 resolve: {6 alias: {7 '@/components': path.resolve(__dirname, 'src/components'),8 '@/lib/utils': path.resolve(__dirname, 'src/lib/utils'),9 },10 },11 });12 };
Run the Shadcn CLI
npx shadcn@latest initAnswer the questions
1 ✔ Preflight checks.2 ✔ Verifying framework. Found Gatsby.3 ✔ Validating Tailwind CSS.4 ✔ Validating import alias.5 √ Which style would you like to use? » New York6 √ Which color would you like to use as the base color? » Neutral7 √ Would you like to use CSS variables for theming? ... no / yes8 ✔ Writing components.json.9 ✔ Checking registry.10 ✔ Updating tailwind.config.js11 ✔ Updating src\styles\global.css12 ✔ Installing dependencies.13 ✔ Created 1 file:14 - src\lib\utils.ts1516 Success! Project initialization completed.17 You may now add components.
Enable gatsby-source-filesystem plugin
Modify you gatsby-config.ts:
1 {2 resolve: `gatsby-source-filesystem`,3 options: {4 name: 'posts',5 path: `./content`,6 },7 },
Create a post template
Inside templates folder create a new file, called post.tsx
1 import * as React from 'react';2 import { graphql, HeadFC, PageProps } from 'gatsby';3 import { MDXProvider } from '@mdx-js/react';45 const PostTemplate: React.FC<PageProps<Queries.GetSinglePostQuery>> = ({6 data: {7 mdx: {8 frontmatter: { title },9 },10 },11 children,12 }) => {13 return (14 <div className='my-16 px-64'>15 <article className='text-lg'>16 <h1 className='tracking-wide text-4xl font-medium space-y-5 my-5'>{title}</h1>17 <MDXProvider> {children}</MDXProvider>18 </article>19 </div>20 );21 };22 export const query = graphql`23 query GetSinglePost($slug: String) {24 mdx(frontmatter: { slug: { eq: $slug } }) {25 frontmatter {26 title27 }28 }29 }30 `;3132 export const Head: HeadFC<Queries.GetSinglePost, unknown> = ({33 data: {34 mdx: {35 frontmatter: { title },36 },37 },38 }) => {39 return (40 <>41 <title>{title}</title>42 </>43 );44 };4546 export default PostTemplate;
Create a page template
1 // page.template.tsx23 import React from 'react';4 import { graphql, HeadFC, PageProps, useStaticQuery } from 'gatsby';56 const PageTemplate: React.FC<PageProps<Queries.GetAllPostsQuery>> = ({7 data: {8 allMdx: { nodes: posts },9 },10 pageContext: { currentPage, numPages, slug = '' },11 }) => {12 return (13 <section className='category mt-[60px] mb-[60px] flex flex-col gap-20 items-center'>14 <h1 className='tracking-wide text-4xl font-bold space-y-5 my-5 font-ptSerif'>15 {`Page ${currentPage}`}16 </h1>17 {posts.map((post) => {18 return (19 <div key={post.id}>20 <h2>{post.frontmatter.title}</h2>21 </div>22 );23 })}24 </section>25 );26 };2728 export const query = graphql`29 query GetAllPosts($skip: Int!, $limit: Int!) {30 allMdx(limit: $limit, skip: $skip) {31 nodes {32 frontmatter {33 slug34 title35 }36 id37 }38 }39 }40 `;4142 export default PageTemplate;
Enable MDX plugin
In gatsby-config.ts add properties to 'gatsby-plugin-mdx'
1 {2 resolve: 'gatsby-plugin-mdx',3 options: {4 extensions: ['.md', '.mdx'],5 },6 },
Set Up GraphQL Queries
Before implementing pagination, ensure that your data sources are set up and that you can query posts, categories, and tags through GraphQL.
1 // gatsby-config.mjs23 import * as path from 'path';45 const POSTS_PER_PAGE = 1;67 const getNumPages = (count) => {8 return Math.ceil(count / POSTS_PER_PAGE) || 1;9 };1011 export const onCreateWebpackConfig = ({ actions }) => {12 actions.setWebpackConfig({13 resolve: {14 alias: {15 '@/components': path.resolve('./', 'src/components'),16 '@/lib/utils': path.resolve('./', 'src/lib/utils'),17 },18 },19 });20 };2122 export const createPages = async ({ graphql, actions, reporter }) => {23 const { createPage } = actions;24 const postTemplate = path.resolve(`src/templates/post.template.tsx`);25 const pageTemplate = path.resolve(`src/templates/page.template.tsx`);2627 const result = await graphql(28 `29 query GatsbyNodeCreatePages {30 allMdx {31 nodes {32 frontmatter {33 slug34 }35 internal {36 contentFilePath37 }38 }39 }40 }41 `,42 );4344 if (result.errors) {45 reporter.panicOnBuild('Error loading MDX result', result.errors);46 }4748 const posts = result.data.allMdx.nodes;4950 posts.forEach((node) => {51 createPage({52 path: `/${node.frontmatter.slug}`,53 component: `${postTemplate}?__contentFilePath=${node.internal.contentFilePath}`,54 context: {55 slug: node.frontmatter.slug,56 },57 });58 });5960 const numPages = getNumPages(posts.length);61 if (numPages > 1) {62 Array.from({ length: numPages }).forEach((_, i) => {63 if (i !== 0) {64 createPage({65 path: `/${i + 1}`,66 component: pageTemplate,67 context: {68 limit: POSTS_PER_PAGE,69 skip: i * POSTS_PER_PAGE,70 numPages,71 currentPage: i + 1,72 },73 });74 }75 });76 }77 };
It's time to test
npm run developIf everything is working, then let's add small helper function - getNumPages() to our lib/utils.ts file:
1 export const POSTS_PER_PAGE = 1;23 export const getNumPages = (count: number): number => {4 return Math.ceil(count / POSTS_PER_PAGE) || 1;5 };
Create a index page
Let's fetch all our posts to index.ts file:
1 import React from 'react';2 import { graphql, type HeadFC, type PageProps } from 'gatsby';34 import { getNumPages } from '../lib/utils';56 const IndexPage: React.FC<PageProps<Queries.IndexPageQuery>> = ({7 data: {8 allMdx: { nodes: posts, totalCount },9 },10 }) => {11 const numPages = getNumPages(totalCount);1213 return (14 <main className='w-full h-full flex items-center flex-col gap-20'>15 <h1 className='text-2xl md:text-3xl my-5 font-ptSerif'>Gatsby Pagination</h1>16 <p className='w-10/12 text-xl'>Pagination in GatsbyJS</p>17 {posts.map((post, idx) => {18 return (19 <div key={post.id}>20 <h2>{post.frontmatter.title}</h2>21 </div>22 );23 })}24 </main>25 );26 };2728 export default IndexPage;2930 export const query = graphql`31 query IndexPage {32 allMdx {33 nodes {34 frontmatter {35 slug36 title37 }38 id39 }40 totalCount41 }42 }43 `;
Then run
npm run clean && npm run developThe result
Navigate to http://localhost:8000/2 to check the pages.
2. Create a Pagination Component
Before creating the pagination component, you need to setup Pagination component from ShadcnUI.
npx shadcn@latest add paginationLast one thing you need to integrate pagination component to Gatsby.*:
Find PaginationLinkProps and PaginationLink inside ui/pagination.tsx, and replace it with:
1 type PaginationLinkProps = {2 isActive?: boolean,3 to: string,4 } & Pick<ButtonProps, 'size'> &5 React.ComponentProps<'a'>;67 const PaginationLink = ({8 className,9 isActive,10 to,11 size = 'icon',12 ...props13 }: PaginationLinkProps) => (14 <Link15 to={`/${to}`}16 aria-current={isActive ? 'page' : undefined}17 className={cn(18 buttonVariants({19 variant: isActive ? 'outline' : 'ghost',20 size,21 }),22 className,23 )}24 {...props}25 />26 );
Don't forget to import the Link from Gatsby.
Now create file posts-pagination.tsx inside your components folder.
1 import React from 'react';23 import {4 Pagination,5 PaginationContent,6 PaginationEllipsis,7 PaginationItem,8 PaginationLink,9 PaginationNext,10 PaginationPrevious,11 } from './ui/pagination';1213 type PostsPaginationProps = {14 numPages: number,15 currentPage: number,16 slug?: string,17 };1819 const PostsPagination = ({ numPages, currentPage, slug = '' }: PostsPaginationProps) => {20 return (21 <Pagination className='mt-10'>22 <PaginationContent>23 {currentPage !== 1 && (24 <PaginationItem>25 <PaginationPrevious to={`${slug}${currentPage - 1 === 1 ? '' : currentPage - 1}`} />26 </PaginationItem>27 )}28 {currentPage - 2 > 1 && (29 <PaginationItem>30 <PaginationEllipsis />31 </PaginationItem>32 )}33 {Array.from({ length: numPages }).map((_, i) => {34 const link = slug + '' + (i === 0 ? '' : i + 1);35 if (i > currentPage + 1 || i < currentPage - 3) {36 return;37 }38 return (39 <PaginationItem key={i}>40 <PaginationLink isActive={i + 1 === currentPage} to={link}>41 {i + 1}42 </PaginationLink>43 </PaginationItem>44 );45 })}46 {numPages > 3 && currentPage + 2 < numPages && (47 <PaginationItem>48 <PaginationEllipsis />49 </PaginationItem>50 )}51 {currentPage !== numPages && (52 <PaginationItem>53 <PaginationNext to={slug + (currentPage + 1)} />54 </PaginationItem>55 )}56 </PaginationContent>57 </Pagination>58 );59 };60 export default PostsPagination;
Let's break down each part of the code:
1. Imports
1 import React from 'react';2 import {3 Pagination,4 PaginationContent,5 PaginationEllipsis,6 PaginationItem,7 PaginationLink,8 PaginationNext,9 PaginationPrevious,10 } from './ui/pagination';
Several UI components from the Shadcn Pagination Component are imported.
2. Component Props
1 type PostsPaginationProps = {2 numPages: number,3 currentPage: number,4 slug?: string,5 };
- numPages: Total number of pages to paginate through.
- currentPage: The current page the user is on.
- slug: An optional string that is appended to the URL for each page link. It defaults to an empty string if not provided. You can provide a string "tag/some-tag" or "category/some-category" to specify the path
3. Ellipsis Before and After Current Page
1 {2 currentPage - 2 > 1 && (3 <PaginationItem>4 <PaginationEllipsis />5 </PaginationItem>6 );7 }
If there are more than two pages between the first page and the current page (i.e., there are skipped pages), it shows an ellipsis (...).
1 {2 numPages > 3 && currentPage + 2 < numPages && (3 <PaginationItem>4 <PaginationEllipsis />5 </PaginationItem>6 );7 }
4. Page Number Links
1 {2 Array.from({ length: numPages }).map((_, i) => {3 const link = slug + '' + (i === 0 ? '' : i + 1);4 if (i > currentPage + 1 || i < currentPage - 3) {5 return;6 }7 return (8 <PaginationItem key={i}>9 <PaginationLink isActive={i + 1 === currentPage} to={link}>10 {i + 1}11 </PaginationLink>12 </PaginationItem>13 );14 });15 }
This dynamically generates pagination links for each page.
This PostsPagination component is a reusable, well-structured pagination system for Gatsby or any React project. It efficiently handles pagination UI, showing a limited range of pages around the current one and allowing navigation via next/previous buttons. The ellipsis keeps the UI clean when dealing with a large number of pages.
Connect Pagination to Index and Page Template
1 const numPages = getNumPages(totalCount);2 {3 numPages > 1 && <PostsPagination currentPage={1} numPages={numPages} />;4 }
Now you've created a reusable pagination component in Gatsby. You can use this component to paginate through posts, categories, or tags. This scalable solution ensures that your content is well-organized and easy to navigate, especially as your dataset grows.