If you only keep a search keyword in component state, it disappears on page refresh, and you can't send a friend a link to the filtered results. Put it in the URL query string instead, and back/forward, bookmarking, and sharing all just work.
The mental model
You can await a page component's searchParams to read q and page. Since users can edit the URL by hand, convert the page number with Number and validate that it's at least 1. If you don't want to hit the server on every keystroke, debounce in a Client Component and change the URL with router.replace.
Let's build it together
export default async function NotesPage({
searchParams,
}: {
searchParams: Promise<{ q?: string; page?: string }>;
}) {
const params = await searchParams;
const query = params.q?.trim() ?? "";
const requestedPage = Number(params.page ?? "1");
const page = Number.isFinite(requestedPage) && requestedPage > 0
? requestedPage
: 1;
const notes = await searchNotes({ query, page });
return <NoteList notes={notes} />;
}How the code works
If q is missing, it defaults to an empty string; if page is invalid, it defaults to 1. The data function only accepts normalized values, so unexpected queries are handled in one place.
Opening /notes?q=next&page=2 shows the second page of results for the keyword Next.5-Minute Try-It
Add a tutorial filter with a difficulty=beginner query, defaulting to all for an unrecognized difficulty value.
Next.js — Fetching Data — Next.js