What’s the best way to implement pagination for a questions list in Next.js?

clock icon

asked 20 days ago

message icon

1 Answers

eye icon

20 Views

I have hundreds of questions in my database, and I want to display them in pages of 10 results each. I’m using Next.js 15 (App Router) with server actions. I’m not sure whether to use cursor-based pagination or offset-based pagination for the best performance. My database is PostgreSQL, and I want it to load smoothly when users click “Next” or “Previous.” What are the pros and cons of each approach here?

1 Answers

Implementing Pagination in Next.js for a Questions List

Implementing efficient pagination in Next.js with PostgreSQL can greatly enhance the performance and usability of your application. Based on your requirements and setup, we need to decide between cursor-based pagination and offset-based pagination. Let’s explore both options, their pros and cons, and why one might be preferred over the other in your scenario.

Offset-Based Pagination

How it Works:

  • Offset-based pagination uses a simple method where you "skip" a set number of entries (based on the page number) and then fetch the amount of data you want to display on each page (e.g., 10 questions per page).
1SELECT * FROM questions ORDER BY id LIMIT 10 OFFSET 20
1SELECT * FROM questions ORDER BY id LIMIT 10 OFFSET 20

Pros:

  • Simple to implement and understand.
  • Works well with small data sets.

Cons:

  • Performance degrades with large datasets because counting and skipping rows becomes slower as you navigate to higher page numbers.
  • It can lead to inconsistent results if data is added or removed between page views.

Cursor-Based Pagination

How it Works:

  • Cursor-based pagination utilizes a pointer (cursor) to a specific row in a database table. The next set of results is fetched by querying data relative to this cursor's position, usually the last item on the current page.
1SELECT * FROM questions WHERE id > last_seen_id ORDER BY id LIMIT 10
1SELECT * FROM questions WHERE id > last_seen_id ORDER BY id LIMIT 10

Pros:

  • More efficient for large datasets as it does not require skipping over rows.
  • Provides consistent results unaffected by additions or deletions in earlier rows.

Cons:

  • Slightly more complex implementation.
  • Users can’t directly jump to a specific page without first passing through previous pages sequentially.

Recommendation for Your Application

For your case, cursor-based pagination is the advisable choice. Given that you have hundreds of questions in your database and performance is a concern, cursor-based pagination will be more efficient. It avoids the slowdowns associated with the high offset values in offset-based pagination. Furthermore, using cursor-based pagination in conjunction with Next.js's App Router and server actions can optimize your application's data fetching strategies even more effectively.

Implementing Cursor-Based Pagination in Next.js

You can utilize Next.js's server-side capabilities to fetch data:

Step 1: Define the Server Action

Create a server action in Next.js to fetch the data based on the cursor.

1// pages/api/questions.js
2
3export async function loader({ request, params }) {
4 const url = new URL(request.url);
5 const lastSeenId = url.searchParams.get("cursor") || 0;
6 const questions = await db.query(
7 `SELECT * FROM questions WHERE id > $1 ORDER BY id LIMIT 10`,
8 [lastSeenId]
9 );
10 return new Response(JSON.stringify(questions.rows));
11}
1// pages/api/questions.js
2
3export async function loader({ request, params }) {
4 const url = new URL(request.url);
5 const lastSeenId = url.searchParams.get("cursor") || 0;
6 const questions = await db.query(
7 `SELECT * FROM questions WHERE id > $1 ORDER BY id LIMIT 10`,
8 [lastSeenId]
9 );
10 return new Response(JSON.stringify(questions.rows));
11}

Step 2: Fetch Data on the Client

Use the Next.js useFetcher from the App Router to handle data fetching on the client side.

1// pages/questions.js
2import { useFetcher } from 'next/navigation';
3
4export default function QuestionsPage() {
5 const fetcher = useFetcher();
6 const cursor = getLastSeenIdFromCurrentQuestions(); // implement this based on your logic
7
8 useEffect(() => {
9 fetcher.load(`/api/questions?cursor=${cursor}`);
10 }, [cursor]);
11
12 // Handle next and previous buttons
13 // Render questions from fetcher.data
14}
1// pages/questions.js
2import { useFetcher } from 'next/navigation';
3
4export default function QuestionsPage() {
5 const fetcher = useFetcher();
6 const cursor = getLastSeenIdFromCurrentQuestions(); // implement this based on your logic
7
8 useEffect(() => {
9 fetcher.load(`/api/questions?cursor=${cursor}`);
10 }, [cursor]);
11
12 // Handle next and previous buttons
13 // Render questions from fetcher.data
14}

In this setup, you alleviate the potential performance issues associated with large datasets and maintain smoother transitions between pages. Make sure to always handle exceptions and potentially empty results to enhance user experience.

Write your answer here

Top Questions