Education
Developer Advocate
Last updated on Jun 19, 2024
Last updated on Jun 19, 2024
Pagination is a crucial feature for improving user experience by breaking down large datasets into manageable chunks. In Next.js, implementing pagination ensures that your application remains performant and user-friendly, especially when dealing with extensive data fetched from an API endpoint.
Pagination helps in dividing content across multiple pages, allowing users to navigate through the data easily. This approach prevents overwhelming users with too much information at once and enhances the overall user experience.
Next.js provides several methods to implement pagination effectively. By leveraging components, dynamic routing, and data fetching methods, you can create a seamless pagination experience.
Let's delve into the key aspects of implementing pagination in your Next.js app.
To start, create a new file for the pagination component. This component will manage the current page state and render buttons for navigating between pages.
Create the Pagination Component File: Create a new file named Pagination.js in the components directory of your Next.js app.
Define the Component: Use the useState and useRouter hooks to manage state and handle routing.
1import { useState } from 'react'; 2import { useRouter } from 'next/router'; 3 4const Pagination = ({ totalPages }) => { 5 const [currentPage, setCurrentPage] = useState(1); 6 const router = useRouter(); 7 8 const handlePageChange = (page) => { 9 setCurrentPage(page); 10 router.push(`?page=${page}`, undefined, { shallow: true }); 11 }; 12 13 return ( 14 <div className="pagination"> 15 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( 16 <button key={page} onClick={() => handlePageChange(page)}> 17 {page} 18 </button> 19 ))} 20 </div> 21 ); 22}; 23 24export default Pagination;
In the code above:
• We define a state variable currentPage to keep track of the currently selected page.
• The handlePageChange function updates the current page and modifies the URL to reflect the new page.
• We render a series of buttons corresponding to the total number of pages.
Next, we will add buttons for navigating to the next and previous pages. This enhances user experience by providing clear navigation options. Update the component to include buttons for navigating to the previous and next pages. Dynamically change the href property of the previous page button to reflect its functionality.
Update the component to include buttons for navigating to the previous and next pages.
1import { useState } from 'react'; 2import { useRouter } from 'next/router'; 3 4const Pagination = ({ totalPages }) => { 5 const [currentPage, setCurrentPage] = useState(1); 6 const router = useRouter(); 7 8 const handlePageChange = (page) => { 9 setCurrentPage(page); 10 router.push(`?page=${page}`, undefined, { shallow: true }); 11 }; 12 13 return ( 14 <div className="pagination"> 15 <button 16 onClick={() => handlePageChange(currentPage - 1)} 17 disabled={currentPage === 1} 18 > 19 Previous 20 </button> 21 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( 22 <button 23 key={page} 24 onClick={() => handlePageChange(page)} 25 className={currentPage === page ? 'active' : ''} 26 > 27 {page} 28 </button> 29 ))} 30 <button 31 onClick={() => handlePageChange(currentPage + 1)} 32 disabled={currentPage === totalPages} 33 > 34 Next 35 </button> 36 </div> 37 ); 38}; 39 40export default Pagination;
In this updated code:
• We add "Previous" and "Next" buttons.
• The buttons are disabled when the user is on the first or last page, respectively.
• We highlight the current page using a conditional class.
To ensure the pagination component integrates well with your application's design, you can use CSS modules or any CSS-in-JS library.
1.pagination { 2 display: flex; 3 justify-content: center; 4 align-items: center; 5 gap: 8px; 6} 7 8button { 9 padding: 8px 16px; 10 border: 1px solid #ccc; 11 background-color: #fff; 12 cursor: pointer; 13} 14 15button:disabled { 16 cursor: not-allowed; 17 opacity: 0.5; 18} 19 20button.active { 21 background-color: #0070f3; 22 color: #fff; 23}
1import { useState } from 'react'; 2import { useRouter } from 'next/router'; 3import styles from './Pagination.module.css'; 4 5const Pagination = ({ totalPages }) => { 6 const [currentPage, setCurrentPage] = useState(1); 7 const router = useRouter(); 8 9 const handlePageChange = (page) => { 10 setCurrentPage(page); 11 router.push(`?page=${page}`, undefined, { shallow: true }); 12 }; 13 14 return ( 15 <div className={styles.pagination}> 16 <button 17 onClick={() => handlePageChange(currentPage - 1)} 18 disabled={currentPage === 1} 19 className={styles.button} 20 > 21 Previous 22 </button> 23 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( 24 <button 25 key={page} 26 onClick={() => handlePageChange(page)} 27 className={`${styles.button} ${currentPage === page ? styles.active : ''}`} 28 > 29 {page} 30 </button> 31 ))} 32 <button 33 onClick={() => handlePageChange(currentPage + 1)} 34 disabled={currentPage === totalPages} 35 className={styles.button} 36 > 37 Next 38 </button> 39 </div> 40 ); 41}; 42 43export default Pagination;
These steps will create a functional and styled pagination component for your Next.js application, ensuring a better user experience for navigating large datasets.
Fetching data in a Next.js app can be efficiently handled using server-side methods like getServerSideProps or getStaticProps. You can also implement search and pagination using URL search params. These methods enable you to fetch data at build time or for each request, ensuring that your data is always up to date.
The getServerSideProps function allows you to fetch data on each request. Here’s an example of how to use it to fetch paginated data from an API endpoint.
1// pages/index.js 2export const getServerSideProps = async ({ query }) => { 3 const page = query.page || 1; 4 const res = await fetch(`https://api.example.com/data?page=${page}`); 5 const data = await res.json(); 6 7 return { 8 props: { 9 data, 10 totalPages: data.totalPages, 11 currentPage: page, 12 }, 13 }; 14}; 15 16const Home = ({ data, totalPages, currentPage }) => { 17 return ( 18 <div> 19 <Pagination totalPages={totalPages} currentPage={currentPage} /> 20 <ul> 21 {data.items.map(item => ( 22 <li key={item.id}>{item.name}</li> 23 ))} 24 </ul> 25 </div> 26 ); 27}; 28 29export default Home;
In the above code:
• The getServerSideProps function fetches data from the API endpoint, including the total number of pages.
• The data and pagination details are passed as props to the Home component.
Once the data is fetched, displaying it on the index page involves mapping through the data items and rendering them as a list or grid.
1const Home = ({ data, totalPages, currentPage }) => { 2 return ( 3 <div> 4 <Pagination totalPages={totalPages} currentPage={currentPage} /> 5 <ul> 6 {data.items.map(item => ( 7 <li key={item.id}>{item.name}</li> 8 ))} 9 </ul> 10 </div> 11 ); 12}; 13 14export default Home;
In this example:
• The Pagination component is used to render the pagination controls.
• The data items are mapped and displayed in an unordered list.
Handling pagination state involves managing the current page and updating it based on user interactions. This is achieved using state hooks and router methods.
1import { useRouter } from 'next/router'; 2 3const Pagination = ({ totalPages, currentPage }) => { 4 const router = useRouter(); 5 6 const handlePageChange = (page) => { 7 router.push(`?page=${page}`, undefined, { shallow: true }); 8 }; 9 10 return ( 11 <div className="pagination"> 12 <button 13 onClick={() => handlePageChange(currentPage - 1)} 14 disabled={currentPage === 1} 15 > 16 Previous 17 </button> 18 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( 19 <button 20 key={page} 21 onClick={() => handlePageChange(page)} 22 className={currentPage === page ? 'active' : ''} 23 > 24 {page} 25 </button> 26 ))} 27 <button 28 onClick={() => handlePageChange(currentPage + 1)} 29 disabled={currentPage === totalPages} 30 > 31 Next 32 </button> 33 </div> 34 ); 35}; 36 37export default Pagination;
1const Home = ({ data, totalPages, currentPage }) => { 2 const router = useRouter(); 3 const { query } = router; 4 const currentPage = parseInt(query.page) || 1; 5 6 return ( 7 <div> 8 <Pagination totalPages={totalPages} currentPage={currentPage} /> 9 <ul> 10 {data.items.map(item => ( 11 <li key={item.id}>{item.name}</li> 12 ))} 13 </ul> 14 </div> 15 ); 16};
With these steps, you can effectively fetch, display, and manage paginated data in your Next.js application. This approach ensures a smooth user experience by allowing easy navigation through large datasets while keeping the application performant and responsive.
To implement a search component in your Next.js app, start by creating a simple search input field that updates the URL parameters as the user types. This ensures that the current search term is reflected in the URL, making it shareable and bookmarkable.
Create the Search Component File: Create a new file named Search.js in the components directory of your Next.js app.
Define the Search Component: Use the useRouter hook from Next.js to update the search query in the URL.
1import { useRouter } from 'next/router'; 2import { useState } from 'react'; 3 4const Search = () => { 5 const [searchTerm, setSearchTerm] = useState(''); 6 const router = useRouter(); 7 8 const handleSearch = (event) => { 9 const query = event.target.value; 10 setSearchTerm(query); 11 12 const params = new URLSearchParams(window.location.search); 13 if (query) { 14 params.set('query', query); 15 } else { 16 params.delete('query'); 17 } 18 router.push(`${router.pathname}?${params.toString()}`, undefined, { shallow: true }); 19 }; 20 21 return ( 22 <input 23 type="text" 24 value={searchTerm} 25 onChange={handleSearch} 26 placeholder="Search..." 27 className="search-input" 28 /> 29 ); 30}; 31 32export default Search;
Managing search parameters involves reading and updating the search parameters in the URL. This can be efficiently handled using Next.js’s router and the URLSearchParams API.
1import { useRouter } from 'next/router'; 2import { useState, useEffect } from 'react'; 3 4const Search = () => { 5 const router = useRouter(); 6 const [searchTerm, setSearchTerm] = useState(''); 7 8 useEffect(() => { 9 const query = new URLSearchParams(window.location.search).get('query'); 10 if (query) { 11 setSearchTerm(query); 12 } 13 }, []); 14 15 const handleSearch = (event) => { 16 const query = event.target.value; 17 setSearchTerm(query); 18 19 const params = new URLSearchParams(window.location.search); 20 if (query) { 21 params.set('query', query); 22 } else { 23 params.delete('query'); 24 } 25 router.push(`${router.pathname}?${params.toString()}`, undefined, { shallow: true }); 26 }; 27 28 return ( 29 <input 30 type="text" 31 value={searchTerm} 32 onChange={handleSearch} 33 placeholder="Search..." 34 className="search-input" 35 /> 36 ); 37}; 38 39export default Search;
To integrate search functionality with pagination, modify the data fetching logic to consider the current search term when making API requests. Ensure that both search term and page number are used to fetch the relevant data.
1// pages/index.js 2export const getServerSideProps = async ({ query }) => { 3 const page = query.page || 1; 4 const searchTerm = query.query || ''; 5 const res = await fetch(`https://api.example.com/data?page=${page}&query=${searchTerm}`); 6 const data = await res.json(); 7 8 return { 9 props: { 10 data, 11 totalPages: data.totalPages, 12 currentPage: page, 13 searchTerm: searchTerm, 14 }, 15 }; 16}; 17 18const Home = ({ data, totalPages, currentPage, searchTerm }) => { 19 return ( 20 <div> 21 <Search /> 22 <Pagination totalPages={totalPages} currentPage={currentPage} /> 23 <ul> 24 {data.items.map(item => ( 25 <li key={item.id}>{item.name}</li> 26 ))} 27 </ul> 28 </div> 29 ); 30}; 31 32export default Home;
In this code:
• The getServerSideProps function fetches data based on both the current page and the search term.
• The Home component receives the search term and passes it to the Search component to maintain consistency.
By following these steps, you can effectively handle search parameters in your Next.js application, ensuring that the search functionality works seamlessly with pagination.
Advanced pagination techniques can enhance the user experience and improve the performance of your Next.js application.
Using query parameters for pagination helps maintain state across page reloads and facilitates sharing URLs with specific pages. Next.js's useRouter hook makes handling query parameters straightforward.
Modify the pagination component to read and update the query parameters.
1import { useRouter } from 'next/router'; 2 3const Pagination = ({ totalPages }) => { 4 const router = useRouter(); 5 const { query } = router; 6 const currentPage = parseInt(query.page) || 1; 7 8 const handlePageChange = (page) => { 9 router.push(`?page=${page}`, undefined, { shallow: true }); 10 }; 11 12 return ( 13 <div className="pagination"> 14 <button 15 onClick={() => handlePageChange(currentPage - 1)} 16 disabled={currentPage === 1} 17 > 18 Previous 19 </button> 20 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( 21 <button 22 key={page} 23 onClick={() => handlePageChange(page)} 24 className={currentPage === page ? 'active' : ''} 25 > 26 {page} 27 </button> 28 ))} 29 <button 30 onClick={() => handlePageChange(currentPage + 1)} 31 disabled={currentPage === totalPages} 32 > 33 Next 34 </button> 35 </div> 36 ); 37}; 38 39export default Pagination;
In this example:
• The current page is derived from the query parameters.
• The handlePageChange function updates the query parameters to reflect the new page.
1export const getServerSideProps = async ({ query }) => { 2 const page = query.page || 1; 3 const res = await fetch(`https://api.example.com/data?page=${page}`); 4 const data = await res.json(); 5 6 return { 7 props: { 8 data, 9 totalPages: data.totalPages, 10 currentPage: page, 11 }, 12 }; 13}; 14 15const Home = ({ data, totalPages, currentPage }) => { 16 return ( 17 <div> 18 <Pagination totalPages={totalPages} currentPage={currentPage} /> 19 <ul> 20 {data.items.map(item => ( 21 <li key={item.id}>{item.name}</li> 22 ))} 23 </ul> 24 </div> 25 ); 26}; 27 28export default Home;
Customizing pagination behavior involves tailoring the pagination logic to fit the specific needs of your application. This might include handling edge cases, customizing the appearance, or adding additional functionality.
1const handlePageChange = (page) => { 2 if (page < 1 || page > totalPages) return; 3 router.push(`?page=${page}`, undefined, { shallow: true }); 4};
1/* Pagination.module.css */ 2.pagination { 3 display: flex; 4 justify-content: center; 5 gap: 10px; 6} 7 8button { 9 padding: 8px 12px; 10 border: 1px solid #ccc; 11 background-color: #f9f9f9; 12 cursor: pointer; 13} 14 15button.active { 16 background-color: #0070f3; 17 color: white; 18}
1import styles from './Pagination.module.css'; 2 3// Use styles in the Pagination component
1const Pagination = ({ totalPages, currentPage }) => { 2 //... existing code 3 return ( 4 <div className={styles.pagination}> 5 <button onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1}> 6 Previous 7 </button> 8 <span>Page {currentPage} of {totalPages}</span> 9 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( 10 <button key={page} onClick={() => handlePageChange(page)} className={currentPage === page ? styles.active : ''}> 11 {page} 12 </button> 13 ))} 14 <button onClick={() => handlePageChange(currentPage + 1)} disabled={currentPage === totalPages}> 15 Next 16 </button> 17 </div> 18 ); 19};
Optimizing pagination for performance ensures that your application remains responsive and efficient, even with large datasets.
Server-Side Data Fetching: Use getServerSideProps or getStaticProps to fetch data efficiently and reduce the load on the client.
Client-Side Data Fetching with Caching: Implement client-side data fetching with caching mechanisms using libraries like react-query or SWR.
1import { useQuery } from 'react-query'; 2 3const fetchPageData = async (page) => { 4 const res = await fetch(`https://api.example.com/data?page=${page}`); 5 return res.json(); 6}; 7 8const Home = () => { 9 const { query } = useRouter(); 10 const currentPage = parseInt(query.page) || 1; 11 12 const { data, isLoading, error } = useQuery(['data', currentPage], () => fetchPageData(currentPage), { 13 keepPreviousData: true, 14 }); 15 16 if (isLoading) return <div>Loading...</div>; 17 if (error) return <div>Error loading data</div>; 18 19 return ( 20 <div> 21 <Pagination totalPages={data.totalPages} currentPage={currentPage} /> 22 <ul> 23 {data.items.map(item => ( 24 <li key={item.id}>{item.name}</li> 25 ))} 26 </ul> 27 </div> 28 ); 29};
1import { useState, useCallback } from 'react'; 2import { debounce } from 'lodash'; 3 4const Search = () => { 5 const [searchTerm, setSearchTerm] = useState(''); 6 const router = useRouter(); 7 8 const handleSearch = useCallback( 9 debounce((query) => { 10 const params = new URLSearchParams(window.location.search); 11 if (query) { 12 params.set('query', query); 13 } else { 14 params.delete('query'); 15 } 16 router.push(`${router.pathname}?${params.toString()}`, undefined, { shallow: true }); 17 }, 300), 18 [] 19 ); 20 21 return ( 22 <input 23 type="text" 24 value={searchTerm} 25 onChange={(e) => { 26 setSearchTerm(e.target.value); 27 handleSearch(e.target.value); 28 }} 29 placeholder="Search..." 30 className="search-input" 31 /> 32 ); 33}; 34 35export default Search;
By employing these advanced techniques, you can build a robust, user-friendly pagination system in your Next.js application that performs well even with large datasets.
Implementing pagination in a Next.js application enhances user experience and performance, especially when dealing with large datasets. By using query parameters for pagination, customizing pagination behavior, and optimizing for performance, you can create a robust and efficient navigation system.
Leveraging Next.js features like getServerSideProps and integrating with libraries such as react-query can significantly streamline data fetching and state management. These advanced techniques ensure your application remains responsive and user-friendly.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.