DEV Community

Cover image for Next.js Query String Params: searchParams + useRouter
Mahdi BEN RHOUMA
Mahdi BEN RHOUMA

Posted on Originally published at iloveblogs.blog

Next.js Query String Params: searchParams + useRouter

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 searchParams prop
  • 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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

Two details matter:

  1. useSearchParams() is read-only.
  2. In the App Router docs, Next.js explicitly recommends the page searchParams prop 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..." />
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

References

Related


Originally published at https://www.iloveblogs.blog

Top comments (0)