How to Get Query Params in Next.js: The Most Efficient Method

The most efficient way to get query params in Next.js is using the useRouter hook from next/router, which provides a cached router.query object containing all URL parameters without re-parsing on every render.

When building applications with Next.js, accessing URL query parameters is a common requirement for features like search filters, pagination, and referral tracking. The framework provides a built-in solution that eliminates manual URL parsing and ensures optimal performance through internal caching mechanisms.

Why useRouter is the Optimal Solution for Next.js Query Params

Next.js implements a singleton router pattern that parses the URL once during navigation and stores the resulting query parameters in an internal state object. This architecture makes router.query access extremely fast because it returns a cached plain object rather than parsing window.location.search on every component render.

According to the Next.js source code in packages/next/src/client/router.ts, the router singleton forwards property accesses including query via defined getters to the underlying Router instance. The core Router class in packages/next/src/shared/lib/router/router.ts maintains the query object as part of its immutable state, updating it only when navigation occurs through the change method.

How to Get Query Params in Next.js: Three Implementation Methods

Method 1: useRouter Hook in Functional Components

The useRouter hook is the standard approach for functional components. It reads the router instance from React context and returns the cached query object immediately.

import { useRouter } from 'next/router'

export default function SearchPage() {
  const { query } = useRouter()
  
  // query is typed as ParsedUrlQuery { [key: string]: string | string[] | undefined }
  const searchTerm = query.term as string
  const pageNumber = query.page || '1'
  
  return (
    <div>
      <h1>Results for: {searchTerm}</h1>
      <p>Page: {pageNumber}</p>
    </div>
  )
}

The hook implementation in packages/next/src/client/router.ts (lines 138-146) simply accesses RouterContext and returns the current router value, ensuring components re-render automatically when the router state changes.

Method 2: withRouter HOC in Class Components

For class-based components, Next.js provides the withRouter higher-order component that injects the router as a prop.

import { withRouter, NextRouter } from 'next/router'
import { Component } from 'react'

interface Props {
  router: NextRouter
}

class ProductDetail extends Component<Props> {
  render() {
    const { id, category } = this.props.router.query
    
    return (
      <div>
        <h1>Product {id}</h1>
        <p>Category: {category}</p>
      </div>
    )
  }
}

export default withRouter(ProductDetail)

The withRouter implementation in packages/next/src/client/with-router.tsx wraps the component and passes the singleton router from context as a prop, providing the same query object available via the hook.

Method 3: Server-Side Access in getServerSideProps

When rendering on the server, query parameters are available through the context object passed to data fetching functions.

import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { term, page = '1' } = context.query
  
  // Fetch data based on query params
  const data = await fetchSearchResults(term as string, parseInt(page as string))
  
  return {
    props: {
      results: data,
      searchTerm: term || ''
    }
  }
}

While getServerSideProps runs on the server for the initial render, the client-side component should still use useRouter for subsequent client-side navigations to avoid unnecessary server round-trips.

Architecture Deep Dive: How Next.js Caches Query Parameters

Understanding why useRouter is efficient requires examining the internal architecture. The Next.js router implements several optimization strategies:

Singleton Pattern with Lazy Initialization

The client-side router is created as a singleton in packages/next/src/client/router.ts. The singletonRouter object uses property descriptors to forward accesses to the actual Router instance only when needed, preventing unnecessary initialization during module load.

Immutable State Management

The core Router class in packages/next/src/shared/lib/router/router.ts maintains an immutable state object containing the query field. When navigation occurs via the change method, the URL is parsed once using the internal parseRelativeUrl utility, and the resulting query object is stored in state. Subsequent reads of router.query return this cached object reference.

React Context Integration

The useRouter hook (lines 138-146 in packages/next/src/client/router.ts) retrieves the router from RouterContext. Because the router reference is stable and the query object is part of the router's state, React's change detection efficiently triggers re-renders only when the query actually changes, not on every render cycle.

This architecture ensures that accessing query parameters via useRouter has O(1) complexity with no string parsing overhead, making it significantly more efficient than manually parsing window.location.search with URLSearchParams on every render.

Summary

  • Use useRouter from next/router to access query params in functional components—it's the most efficient method due to internal caching.
  • Access router.query to get a plain object containing all URL parameters; values may be strings or arrays for repeated keys.
  • Use withRouter for class components to inject the same router instance as a prop.
  • Server-side fetching via getServerSideProps receives context.query with the same shape for initial renders.
  • Avoid manual parsing of window.location—the Next.js router parses the URL once during navigation and caches the result in packages/next/src/shared/lib/router/router.ts.

Frequently Asked Questions

How do I handle array values in Next.js query params?

When a URL contains repeated query keys (e.g., ?tag=react&tag=nextjs), Next.js automatically parses them as an array in router.query (e.g., { tag: ['react', 'nextjs'] }). Always check if a value is an array using Array.isArray() before processing, or normalize it with [].concat(value) to handle both single and multiple values consistently.

What is the difference between using useRouter in the Pages Router versus the App Router?

In the Pages Router (pages/ directory), you import useRouter from next/router and access router.query. In the App Router (app/ directory), you use useSearchParams from next/navigation instead, which returns a URLSearchParams object rather than a plain object. The Pages Router's useRouter caches the parsed query object, while the App Router's useSearchParams reads from the current URL search params directly.

Can I use useRouter in getServerSideProps or getStaticProps?

No, useRouter is a client-side only hook that relies on React context. In getServerSideProps or getStaticProps, access query parameters through the context argument's query property instead: context.query. This provides the same shape of data (an object with string or array values) but works during server-side rendering before the React hydration occurs.

Is router.query available during the initial render of a page?

In the Pages Router, router.query may be empty during the initial server-side render or the first client-side render for statically optimized pages, because Next.js needs to parse the URL first. For server-rendered pages using getServerSideProps, the query is populated immediately. For client-side transitions, router.query updates instantly. To handle the initial empty state, use conditional rendering or check if router.isReady is true before accessing query values.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →