A Next.js App Router page needs the ?sort=price query string. Which file receives the searchParams prop, why does a layout.tsx not get one, and what is the resolved value's type?
answer
- path half versus query half
- which file the prop reaches
- layouts survive navigation
- a stale value is worse than no value
- repeated keys are not strings
basics
~20 sOnly page.tsx receives the searchParams prop — a Promise in Next 15 and later that resolves to the parsed query string. Layouts do not: they are preserved and not re-rendered when only the query changes, so a searchParams prop there would go stale.
solid answer
~50 s`searchParams` is passed to the **page** of a route and to `generateMetadata` — never to `layout.tsx`. In Next 15 and later it is a Promise, so a Server Component page writes `const { sort } = await searchParams`. Each value is `string | string[] | undefined`: a key repeated in the URL, `?tag=a&tag=b`, resolves to `['a','b']`, so code that assumes a plain string breaks on that input. Layouts are excluded deliberately: a layout is preserved across client navigations within its subtree and is not re-rendered when only the query string changes, so a `searchParams` prop on it would silently serve stale values. If something inside a layout needs the query, either read it in a Client Component with `useSearchParams`, or read it in the page and pass it down. Reading `searchParams` in a page also means that page renders per request rather than being served from a build-time prerender.
go deeper
Know that the path lives in params and the query string lives in searchParams, and that searchParams is handed to the page component. Say that it is awaited in current Next.
Explain that only the page and generateMetadata receive it, give the real reason layouts do not — they are preserved and not re-rendered when only the query changes — and handle the string-or-array value type.
Show where to place the read: server page versus a client subtree with the hook, what that choice does to the route's rendering strategy, and how you validate query values before they reach a data layer.
Own what belongs in the URL at all. Weigh shareable, indexable query state against server state and cacheability, and set a house convention for filter parameter naming and validation across teams.
## Two different props, two different halves of the URL The App Router splits a URL into two props. `params` carries the **path** segments matched by bracketed folders. `searchParams` carries the **query string** — everything after `?`. For `/products/42?sort=price&tag=a&tag=b`, a page at `app/products/[id]/page.tsx` sees `params` resolving to `{ id: '42' }` and `searchParams` resolving to `{ sort: 'price', tag: ['a','b'] }`. The halves behave differently because they *are* different: the path decides which route matches, and the query does not participate in routing at all. Any query string reaches the same route. ## Only the page gets it ```tsx // app/products/page.tsx export default async function Page({ searchParams, }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) { const { sort } = await searchParams return <ProductList sort={typeof sort === 'string' ? sort : 'default'} /> } ``` `generateMetadata` in the same file receives it too, which is how you build a title that reflects an active filter. `layout.tsx` receives `params` but **not** `searchParams`, and that omission is the part interviewers like to probe. ## Why layouts are excluded A layout exists to survive navigation. When the user moves between routes inside a layout's subtree, Next preserves that layout — its DOM stays mounted and its state, such as an open sidebar or a scroll position, is kept. Concretely, that means the layout is **not re-rendered** on a navigation that only changes the query string, for instance `/products?sort=price` to `/products?sort=name`. If `layout.tsx` had a `searchParams` prop, it would therefore be holding the value from whenever the layout last rendered — often the very first URL the user landed on — while the page below it displayed the current one. Rather than ship a prop that is correct on first load and quietly wrong afterwards, Next does not pass it at all. Understanding that reasoning is more valuable in an interview than memorising the rule, because it also tells you where the query genuinely can be read. ## Where to read the query instead - **In the page**, passing what is needed down as ordinary props to whatever renders it. - **In a Client Component** anywhere in the tree, with the `useSearchParams` hook, which is subscribed to navigation and therefore stays current. The first option keeps the work on the server; the second is what you need when the consumer is a client-side control such as a filter dropdown that must reflect the URL as it changes. ## The value's type is not just string Query strings allow repeated keys, so the type of any entry is `string | string[] | undefined`: ```tsx const { tag } = await searchParams const tags = tag === undefined ? [] : Array.isArray(tag) ? tag : [tag] ``` Code written as `tag.toLowerCase()` throws the moment a user, or a shared link, contains `?tag=a&tag=b`. Normalising once at the top of the page is the habit to demonstrate. And as with path segments, the values are arbitrary user input: a `sort` parameter interpolated into a database query without an allow-list is a genuine injection surface, not a hypothetical one. ## Consequences for rendering A query string is only known when a request arrives, so a page that reads `searchParams` produces per-request output rather than being served from a build-time prerender. Practically this means you read it where it is genuinely needed rather than sprinkling it around, and that such a page cannot also be a fully static asset. Where the query only affects a small part of the page, pushing that part into a Client Component that uses the hook keeps the rest of the route's rendering strategy intact. ## The compact answer "`searchParams` is a page-only prop, it is a Promise in current Next, and its values are `string | string[] | undefined`. Layouts do not get it because they are not re-rendered when only the query changes, so it would be stale — read it in the page, or with the client hook where a client component needs it."
- For the URL /search?tag=a&tag=b, what does the resolved searchParams.tag equal?The array `['a','b']`. Query strings permit repeated keys, so every entry is typed `string | string[] | undefined`. Code that calls a string method on it throws as soon as a real user shares a multi-select filter link. Normalise once — coerce to an array, or take the first value deliberately — before using it.
- A filter dropdown inside a layout must reflect the current query string. How do you wire that up?Make it a Client Component and read `useSearchParams`, which stays subscribed to navigation and re-renders when the query changes. The layout itself still has no `searchParams` prop; the hook is what gives a client subtree a live view of the query even though the server layout around it is not re-rendered.
- Does reading searchParams affect how the page is rendered?Yes. The query string is only known per request, so a page that reads `searchParams` produces per-request output rather than being served from a build-time prerender. If only a small widget depends on the query, moving that widget into a Client Component that uses the hook keeps the rest of the route's rendering strategy unchanged.
saying these in an interview costs you the question
- Expects layout.tsx to receive a searchParams prop
- Assumes every searchParams value is a plain string
- Looks for query parameters inside params
- Reads searchParams synchronously in Next 15
- Interpolates a sort parameter straight into a query