How to get query string parameters in Next.js
If `router.query` examples keep breaking on you, the problem is usually that you're mixing App Router and Pages Router APIs. Here is the exact fix for each case.
How to get query string parameters in Next.js#
The symptom is simple: you open /dashboard?search=invoice&page=2, copy an old snippet, and get undefined, stale values, or the wrong API entirely.
The root cause is that Next.js now has two routing models, and the correct query-string API depends on where you read the params:
- App Router Server Component page: use the
searchParamsprop - App Router Client Component: use
useSearchParams() - Shared client component across both routers:
useSearchParams()still works
Here is the exact fix for each case.
The App Router server-side fix#
If you are inside app/.../page.tsx, use the page prop. In the current Next.js docs, searchParams is a promise in modern App Router pages.
// app/dashboard/page.tsx
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const { search = '', page = '1' } = await searchParams
return (
<main>
<h1>Dashboard</h1>
<p>Search: {search}</p>
<p>Page: {page}</p>
</main>
)
}
Use this when the query string affects data fetching, pagination, filtering, or metadata for the page itself.
The App Router client-side fix#
If the component is interactive and already marked 'use client', use useSearchParams() from next/navigation.
'use client'
import { useSearchParams } from 'next/navigation'
export default function SearchSummary() {
const searchParams = useSearchParams()
const search = searchParams.get('search') ?? ''
const page = searchParams.get('page') ?? '1'
return (
<p>
Searching for <strong>{search || 'everything'}</strong> on page {page}
</p>
)
}
Two details matter:
useSearchParams()is read-only.- In the App Router docs, Next.js explicitly recommends the page
searchParamsprop if you are already in a Server Component page.
The shared-component pattern that survives both routers#
This is the cleanest answer if you are migrating gradually or sharing a search bar between pages/ and app/.
'use client'
import { useSearchParams } from 'next/navigation'
export function SearchBar() {
const searchParams = useSearchParams()
if (!searchParams) {
return <input defaultValue="" placeholder="Search..." />
}
const search = searchParams.get('search') ?? ''
return <input defaultValue={search} placeholder="Search..." />
}
That fallback is important in Pages Router prerendering. The official Pages Router docs now document useSearchParams from next/navigation as a shared option across both routers.
The mistake that keeps causing confusion#
Most search results still mix old Pages Router answers with new App Router code.
If you copied something like this into app/page.tsx:
// Wrong mental model for a modern App Router page
const query = router.query.search
you are using the wrong abstraction. In the App Router, the page itself should usually read searchParams, and client components should use useSearchParams.
Which API should you choose?#
Use the page prop when:
- the param changes the server query
- the param affects SEO or pagination
- the page should render from the URL alone
Use useSearchParams() when:
- the component is client-only
- you need live access after client navigation
- you are building a reusable search or filter widget
The production-safe rule#
If the URL controls what data the page loads, read it on the server first and pass stable values downward. That keeps your route easy to reason about and avoids a lot of hydration and loading-state noise.
If you are already untangling router differences, these are worth reading next:
- Next.js App Router Guide: From Basics to Advanced Patterns
- How to Access Route Parameter Inside getServerSideProps
- Next.js 15 Middleware: Complete Guide to Auth, Rate Limiting, A/B Testing, and Edge Logic
- Fix "cookies() should be awaited" Error in Next.js 15
References#
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Next.js Redirect from / to another page
If your redirect works only after render, flickers, or fires in the wrong place, you are probably using the wrong redirect API for the job. Here is the exact mapping.
Why useEffect runs twice in Next.js dev
If your logs, API calls, or subscriptions fire twice in local dev, you are probably seeing React's development-only Strict Mode check. Here is what to fix and what not to panic about.
Fix "cookies() should be awaited" Error in Next.js 15
Next.js 15 broke synchronous `cookies().get()`. Every server-side call must now `await cookies()` first. Here's the precise migration — App Router pages, route handlers, Server Actions, and Supabase SSR — plus the codemod that fixes 90% of call sites automatically.
Browse by Topic
Find stories that matter to you.
