javascript 54 lines · 6 steps

A searchable, paginated table as a Next.js Server Component

An async Server Component reads URL params, queries Prisma, and renders a paginated, filterable invoice table.

Explained by highlit
1import Link from "next/link";
2import { prisma } from "@/lib/prisma";
3 
4const PAGE_SIZE = 20;
5 
6export default async function InvoicesTable({ searchParams }) {
7 const params = await searchParams;
8 const query = (params.q ?? "").trim();
9 const page = Math.max(1, Number(params.page) || 1);
10 
11 const where = query
12 ? { customer: { name: { contains: query, mode: "insensitive" } } }
13 : {};
14 
15 const [invoices, total] = await Promise.all([
16 prisma.invoice.findMany({
17 where,
18 include: { customer: true },
19 orderBy: { issuedAt: "desc" },
20 skip: (page - 1) * PAGE_SIZE,
21 take: PAGE_SIZE,
22 }),
23 prisma.invoice.count({ where }),
24 ]);
25 
26 const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
27 const hrefFor = (p) => `?${new URLSearchParams({ ...(query && { q: query }), page: String(p) })}`;
28 
29 return (
30 <div>
31 <table>
32 <thead>
33 <tr><th>Invoice</th><th>Customer</th><th>Amount</th><th>Status</th></tr>
34 </thead>
35 <tbody>
36 {invoices.map((inv) => (
37 <tr key={inv.id}>
38 <td>{inv.number}</td>
39 <td>{inv.customer.name}</td>
40 <td>${(inv.amountCents / 100).toFixed(2)}</td>
41 <td>{inv.status}</td>
42 </tr>
43 ))}
44 </tbody>
45 </table>
46 
47 <nav aria-label="Pagination">
48 {page > 1 && <Link href={hrefFor(page - 1)}>Previous</Link>}
49 <span>Page {page} of {totalPages}</span>
50 {page < totalPages && <Link href={hrefFor(page + 1)}>Next</Link>}
51 </nav>
52 </div>
53 );
54}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Async Server Components let you await data directly in the component body, skipping client-side fetching.
  2. 2Driving search and pagination from URL params makes state shareable, bookmarkable, and refresh-safe.
  3. 3Running the page query and count in parallel with Promise.all avoids serializing two round-trips.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A searchable, paginated table as a Next.js Server Component — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code