# How to Access and Manage React Router Query Params in Next.js Components

> Efficiently access and manage react router query params in Nextjs components using useRouter or useSearchParams hooks. Get parsed query objects with shallow navigation for optimal performance.

- Repository: [Vercel/next.js](https://github.com/vercel/next.js)
- Tags: how-to-guide
- Published: 2026-02-16

---

**The most efficient way to handle react router query params in Next.js is using the `useRouter` hook (Pages Router) or `useSearchParams` hook (App Router), which provide immutable, parsed query objects that update efficiently via shallow navigation without triggering full page reloads.**

Next.js provides a built-in routing solution for React applications that eliminates the need for external routing libraries. Whether you're using the legacy Pages Router or the modern App Router, accessing and managing react router query params follows a consistent pattern optimized for performance. This guide examines the implementation details from the vercel/next.js source code to show you the most efficient patterns for reading and updating URL parameters.

## Understanding Next.js Router Architecture

Next.js maintains two distinct routing systems. The **Pages Router** (`next/router`) uses a singleton pattern where `useRouter` returns a router instance with a parsed `query` object. The **App Router** (`next/navigation`) uses React Server Components by default and exposes `useSearchParams` for client components, returning a `URLSearchParams` instance.

Both systems prioritize immutability and shallow comparison to minimize re-renders. According to the source code in [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts), the router creates a shallow copy of the query object via `Object.assign` in `makePublicRouterInstance`, ensuring components receive stable references unless the URL actually changes.

## Accessing Query Params in the Pages Router

For applications using the `pages/` directory, import `useRouter` from `next/router`. The hook returns a router object where the `query` property contains the parsed URL parameters.

```tsx
import { useRouter } from 'next/router'

export default function ProductList() {
  const router = useRouter()
  const { query } = router
  
  // Access specific parameters
  const page = Number(query.page ?? 1)
  const category = query.category as string

  return (
    <div>
      <h1>Category: {category}</h1>
      <p>Page: {page}</p>
    </div>
  )
}

```

The `query` object is typed as `ParsedUrlQuery` and provides an immutable snapshot of the current URL state. As implemented in [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts) lines 69-82, the `makePublicRouterInstance` function shallow-copies the query to prevent accidental mutations of the internal router state.

## Accessing Query Params in the App Router

For the `app/` directory, use `useSearchParams` from `next/navigation`. This hook returns a `URLSearchParams` instance that provides a Map-like interface for reading query strings.

```tsx
'use client'

import { useSearchParams } from 'next/navigation'

export default function Dashboard() {
  const searchParams = useSearchParams()
  
  // Read individual parameters
  const tab = searchParams.get('tab') ?? 'overview'
  const view = searchParams.get('view') ?? 'grid'

  return (
    <div>
      <h1>Current Tab: {tab}</h1>
      <p>View Mode: {view}</p>
    </div>
  )
}

```

According to [`packages/next/src/client/components/navigation.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/components/navigation.ts) lines 146-150, `useSearchParams` wraps the native `URLSearchParams` constructor to ensure compatibility with React's rendering cycle while maintaining the standard Web API interface.

## Efficient Update Patterns with Shallow Navigation

Updating react router query params without triggering a full page reload requires **shallow navigation**. Both routers support this pattern, though the implementation differs slightly.

### Pages Router Updates

Use `router.replace` with the `shallow: true` option to update the URL and query object without running data fetching methods like `getServerSideProps` or `getStaticProps`.

```tsx
import { useRouter } from 'next/router'

export default function ProductList() {
  const router = useRouter()
  const { query, pathname } = router
  const page = Number(query.page ?? 1)

  const goToPage = (nextPage: number) => {
    // Preserve existing query params while updating specific values
    router.replace(
      {
        pathname,
        query: { ...query, page: nextPage },
      },
      undefined,
      { shallow: true } // Critical: prevents data refetch
    )
  }

  return (
    <div>
      <p>Current page: {page}</p>
      <button onClick={() => goToPage(page - 1)} disabled={page <= 1}>
        Prev
      </button>
      <button onClick={() => goToPage(page + 1)}>Next</button>
    </div>
  )
}

```

As shown in [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts), shallow navigation skips the data fetching phase while still emitting the route change event and updating the `query` snapshot.

### App Router Updates

In the App Router, shallow navigation is the default behavior for client-side transitions. Use `router.replace` from `next/navigation` to update search parameters.

```tsx
'use client'

import { useSearchParams, useRouter } from 'next/navigation'

export default function Dashboard() {
  const searchParams = useSearchParams()
  const router = useRouter()
  const tab = searchParams.get('tab') ?? 'overview'

  const setTab = (newTab: string) => {
    // Clone to avoid mutating the original read-only instance
    const params = new URLSearchParams(searchParams)
    params.set('tab', newTab)

    // Shallow replace updates URL without server round-trip
    router.replace(`?${params.toString()}`, { shallow: true })
  }

  return (
    <>
      <nav>
        <button onClick={() => setTab('overview')}>Overview</button>
        <button onClick={() => setTab('stats')}>Stats</button>
      </nav>
      <section>{tab === 'overview' ? <Overview /> : <Stats />}</section>
    </>
  )
}

```

## Implementation Details from Source Code

The efficiency of Next.js query parameter handling stems from architectural decisions visible in the vercel/next.js repository.

**Singleton Router Pattern**: In [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts), the router is created as a singleton via `createRouter`. This ensures that all components calling `useRouter` receive the same instance, eliminating duplicate URL parsing overhead. The `useRouter` implementation (lines 38-46) simply reads from `RouterContext`.

**Immutable Query Snapshots**: The `makePublicRouterInstance` function (lines 69-82 in [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts)) uses `Object.assign` to create a shallow copy of the `query` object. This guarantees React's reference equality checks work correctly—components only re-render when the URL actually changes, not when the router instance mutates internal state.

**Shallow Navigation Optimization**: The router's `change` method distinguishes between shallow and deep navigation. When `shallow: true` is passed to `router.replace`, the method skips data fetching hooks (`getServerSideProps`, etc.) and only updates the URL bar and internal `query` state, triggering a fast client-side re-render.

## Summary

- **Use `useRouter`** from `next/router` (Pages Router) or **`useSearchParams`** from `next/navigation` (App Router) to read react router query params efficiently.
- **Query objects are immutable snapshots** created via shallow copy in `makePublicRouterInstance`, ensuring stable references for React's rendering optimization.
- **Update parameters with `router.replace`** and `{ shallow: true }` to modify the URL without data refetching, ideal for client-side filtering and pagination.
- **App Router uses `URLSearchParams`** interface while Pages Router uses a plain object (`ParsedUrlQuery`), requiring different cloning strategies when updating.

## Frequently Asked Questions

### What is the difference between `useRouter` and `useSearchParams`?

`useRouter` is the legacy hook for the Pages Router (`next/router`) that returns a router object containing the `query` property as a `ParsedUrlQuery` object. `useSearchParams` is the App Router (`next/navigation`) hook that returns a `URLSearchParams` instance, providing a Map-like API with methods like `.get()` and `.set()`. The App Router version is read-only and requires `useRouter` from the same module to perform navigation updates.

### How do I prevent full page reloads when updating query parameters?

Use **shallow navigation** by passing `{ shallow: true }` as the third argument to `router.replace()` in the Pages Router, or as an option object in the App Router. This updates the URL bar and the internal router state without triggering data fetching methods like `getServerSideProps` or server component re-rendering. According to the source code in [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts), shallow navigation skips the data fetching phase while still emitting the route change event and updating the `query` snapshot.

### Can I directly mutate the query object returned by `useRouter`?

No, you should never mutate the `query` object directly. The Next.js source code in [`packages/next/src/client/router.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/router.ts) creates an immutable snapshot via `Object.assign` in the `makePublicRouterInstance` function (lines 69-82). While direct mutation might not throw an immediate error, it violates React's immutability principles and can cause unpredictable rendering behavior or stale data. Always treat the query object as read-only and use `router.replace()` with a new query object to update parameters.

### Why does the App Router use `URLSearchParams` instead of a plain object?

The App Router adopts the standard Web API `URLSearchParams` interface to align with modern browser standards and provide a consistent API for handling search parameters. Unlike the Pages Router's plain object (`ParsedUrlQuery`), `URLSearchParams` automatically handles edge cases like duplicate keys and provides convenient methods like `.get()`, `.getAll()`, `.has()`, and `.set()`. As implemented in [`packages/next/src/client/components/navigation.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/client/components/navigation.ts) lines 146-150, this approach ensures type safety and immutability while maintaining compatibility with standard URL manipulation patterns.