Adding Search and Pagination
Search text and page numbers belong in the URL (?q=react&page=2), not only in useState. Users can bookmark, share, and use the back button. Server Components can read searchParams and filter on the server.
URL as state — searchable, shareable, refresh-safe
Search text and page numbers belong in the URL (?q=react&page=2), not only in useState. Users can bookmark, share, and use the back button. Server Components can read searchParams and filter on the server.
Real-life example: URL state is like writing the aisle and shelf number on a shopping list — anyone with the note finds the same spot.
searchParams in page.tsx
In the App Router, the page receives searchParams. Treat them as the source of truth for filters.
import Link from "next/link";
import { sql } from "@vercel/postgres";
type Props = {
searchParams: Promise<{ q?: string; page?: string }>;
};
const PAGE_SIZE = 10;
export default async function InvoicesPage({ searchParams }: Props) {
const { q = "", page = "1" } = await searchParams;
const current = Math.max(1, Number(page) || 1);
const offset = (current - 1) * PAGE_SIZE;
const { rows } = await sql`
SELECT id, customer_name, amount, status
FROM invoices
WHERE customer_name ILIKE ${"%" + q + "%"}
ORDER BY id DESC
LIMIT ${PAGE_SIZE} OFFSET ${offset}
`;
return (
<main>
<form>
<label htmlFor="q">Search customers</label>
<input id="q" name="q" defaultValue={q} placeholder="Asha..." />
<button type="submit">Search</button>
</form>
<ul>
{rows.map((row) => (
<li key={row.id}>
{row.customer_name} — ₹{row.amount} ({row.status})
</li>
))}
</ul>
<nav aria-label="Pagination">
{current > 1 && (
<Link href={`/invoices?q=${encodeURIComponent(q)}&page=${current - 1}`}>
Previous
</Link>
)}
<span>Page {current}</span>
<Link href={`/invoices?q=${encodeURIComponent(q)}&page=${current + 1}`}>
Next
</Link>
</nav>
</main>
);
}Pagination with Link
Prefer <Link> for page changes so Next.js can prefetch. Keep q in every page link so filters survive when you flip pages.
Real-life example: Pagination links are page numbers in a book index — each link must remember which chapter (filter) you were reading.
- Encode query strings with encodeURIComponent
- Clamp page to >= 1
- Disable or hide Next when the last page has fewer than PAGE_SIZE rows