Next.js cookbook · Beginner
Paginate and filter with searchParams in Server Components
Build a searchable, paginated product list rendered on the server and shareable via URL.
Approach
Read searchParams (a Promise in Next.js 15+) in the page, validate it, and fetch on the server. A small client component updates the URL with useRouter().replace() (debounced for search), which re-renders the server page. Wrap results in Suspense keyed by the params for loading states.
Solution
// app/products/page.tsx
import { Suspense } from "react";
export default async function ProductsPage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {
const sp = await searchParams;
const q = (sp.q ?? "").slice(0, 100);
const page = Math.max(1, Number(sp.page) || 1);
return (
<main>
<SearchBox defaultValue={q} />
<Suspense key={`${q}-${page}`} fallback={<ResultsSkeleton />}>
<Results q={q} page={page} />
</Suspense>
</main>
);
}
async function Results({ q, page }: { q: string; page: number }) {
const { items, totalPages } = await searchProducts({ q, page, pageSize: 24 });
return (<><ProductGrid items={items} /><Pagination page={page} totalPages={totalPages} q={q} /></>);
}
// app/products/SearchBox.tsx
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useDebouncedCallback } from "use-debounce";
export function SearchBox({ defaultValue }: { defaultValue: string }) {
const router = useRouter();
const pathname = usePathname();
const params = useSearchParams();
const onChange = useDebouncedCallback((value: string) => {
const next = new URLSearchParams(params);
value ? next.set("q", value) : next.delete("q");
next.delete("page");
router.replace(`${pathname}?${next}`);
}, 300);
return <input type="search" aria-label="Search products" defaultValue={defaultValue} onChange={(e) => onChange(e.target.value)} />;
}
Pitfalls
- Forgetting
await searchParamsin Next.js 15+. - Pushing history entries on every keystroke.
- Using searchParams makes the page dynamic — cache the data layer instead.
Variations
nuqsfor type-safe URL state in Next.js.
Further reading: Next.js: page searchParams
Reference: https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional
More Next.js recipes
- Add a Content Security Policy with nonces in Next.js
- Add dark mode to Next.js without a flash
- Add SEO metadata, Open Graph and sitemaps
- Avoid data-fetching waterfalls in Server Components
- Build a validated JSON API with Route Handlers
- Create encrypted session cookies in Next.js
- Handle errors and 404s in the App Router
- Handle forms with Server Actions and validation