Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Server-side pagination is the heavy lifter’s choice for managing large datasets efficiently. It reduces the load on the client, improves initial page load times, and plays nicely with SEO. Let’s dive into building a robust server-side pagination system using Next.js 14+ and its app router.
First, make sure you’re running Next.js 14 or later. If not, let’s get you set up:
npx create-next-app@latest pagination-demo
cd pagination-demo
Choose TypeScript, ESLint, Tailwind CSS, and the App Router when prompted.
We’ll start by creating a utility function to handle pagination calculations. Create a new file utils/pagination.ts
:
export function getPagination(page: number, size: number) {
const limit = size ? +size : 3;
const from = page ? page * limit : 0;
const to = page ? from + size - 1 : size - 1;
return { from, to };
}
Now, let’s create our API route. In app/api/posts/route.ts
:
import { NextResponse } from 'next/server';
import { getPagination } from '@/utils/pagination';
// Simulated database
const posts = Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
title: `Post ${i + 1}`,
content: `This is the content of post ${i + 1}`
}));
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const size = parseInt(searchParams.get('size') || '10');
const { from, to } = getPagination(page - 1, size);
const paginatedPosts = posts.slice(from, to + 1);
return NextResponse.json({
currentPage: page,
totalPages: Math.ceil(posts.length / size),
pageSize: size,
totalCount: posts.length,
posts: paginatedPosts,
});
}
Create a new file app/posts/page.tsx
:
import Link from 'next/link';
async function getPosts(page = 1, size = 10) {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/posts?page=${page}&size=${size}`, { cache: 'no-store' });
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function Posts({
searchParams
}: {
searchParams: { page?: string, size?: string }
}) {
const page = Number(searchParams.page) || 1;
const size = Number(searchParams.size) || 10;
const { posts, currentPage, totalPages } = await getPosts(page, size);
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Posts</h1>
<ul className="space-y-2">
{posts.map((post: any) => (
<li key={post.id} className="bg-gray-100 p-2 rounded">
{post.title}
</li>
))}
</ul>
<div className="flex justify-center space-x-2 mt-4">
{Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNumber) => (
<Link
key={pageNumber}
href={`/posts?page=${pageNumber}&size=${size}`}
className={`px-3 py-1 rounded ${
currentPage === pageNumber ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}
>
{pageNumber}
</Link>
))}
</div>
</div>
);
}
Create app/posts/loading.tsx
:
export default function Loading() {
return <div>Loading posts...</div>
}
And app/posts/error.tsx
:
'use client'
export default function Error({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
Let’s add some metadata in app/posts/layout.tsx
:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Paginated Posts | Your Awesome Blog',
description: 'Explore our extensive collection of posts with server-side pagination.',
openGraph: {
title: 'Paginated Posts | Your Awesome Blog',
description: 'Explore our extensive collection of posts with server-side pagination.',
type: 'website',
url: 'https://yourawesomeblog.com/posts',
},
}
export default function PostsLayout({
children,
}: {
children: React.ReactNode
}) {
return <>{children}</>
}
To optimize performance, we can implement Incremental Static Regeneration (ISR). Update your app/posts/page.tsx
:
import Link from 'next/link';
async function getPosts(page = 1, size = 10) {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/posts?page=${page}&size=${size}`, {
next: { revalidate: 60 } // Revalidate every 60 seconds
});
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
// ... rest of the component remains the same
You’ve now got a slick server-side pagination system using Next.js 14+ app router. Here are the key takeaways:
searchParams
in server components to handle query parameters.For more on Next.js server components and data fetching, check out the official documentation.
Remember, server-side pagination is your friend when dealing with large datasets or when SEO is crucial. It might require more server resources, but it often results in a snappier, more scalable application.
Happy coding, you pagination wizard!