vercel/next.js · error · ReadonlyURLSearchParamsError

Method unavailable on `ReadonlyURLSearchParams`. Read more:

Error message

Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams

What it means

`useSearchParams()` from `next/navigation` returns a `ReadonlyURLSearchParams` instance that overrides `append()` to throw. The App Router deliberately makes search params read-only on the client because URL state should be managed through the router (router.push/replace), not by mutating the params object directly.

Source

Thrown at packages/next/src/client/components/readonly-url-search-params.ts:23

 */

/** @internal */
class ReadonlyURLSearchParamsError extends Error {
  constructor() {
    super(
      'Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams'
    )
  }
}

/**
 * A read-only version of URLSearchParams that throws errors when mutation methods are called.
 * This ensures that the URLSearchParams returned by useSearchParams() cannot be mutated.
 */
export class ReadonlyURLSearchParams extends URLSearchParams {
  /** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
  append() {
    throw new ReadonlyURLSearchParamsError()
  }
  /** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
  delete() {
    throw new ReadonlyURLSearchParamsError()
  }
  /** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
  set() {
    throw new ReadonlyURLSearchParamsError()
  }
  /** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
  sort() {
    throw new ReadonlyURLSearchParamsError()
  }
}

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Construct a new mutable URLSearchParams from the readonly values, mutate it, then navigate: `const p = new URLSearchParams(searchParams.toString()); p.append('key', 'val'); router.push('?' + p.toString())`.
  2. Use router.push or router.replace with the full updated query string.
  3. Use the `usePathname` + `useRouter` pattern to build the target URL.

Example fix

// before — throws
const searchParams = useSearchParams()
searchParams.append('page', '2')

// after — build a new URL and navigate
const searchParams = useSearchParams()
const router = useRouter()
const params = new URLSearchParams(searchParams.toString())
params.append('page', '2')
router.push(`?${params.toString()}`)
Defensive patterns

Strategy: type-guard

Validate before calling

import { useSearchParams } from 'next/navigation'
// Use a writable copy for mutations
function useMutableSearchParams(): URLSearchParams {
  const readonly = useSearchParams()
  return new URLSearchParams(readonly.toString())
}

Type guard

import { ReadonlyURLSearchParams } from 'next/navigation'
function isReadonlyURLSearchParams(
  v: unknown
): v is ReadonlyURLSearchParams {
  return v instanceof ReadonlyURLSearchParams
}

Prevention

When it happens

Trigger: Calling `.append()` on the value returned by `useSearchParams()`. The class extends URLSearchParams but overrides the mutation method to throw a `ReadonlyURLSearchParamsError`.

Common situations: Migrating from Pages Router where URLSearchParams was fully mutable; using a URL manipulation helper that expects to call .append(); adding a query param to the current URL in an event handler.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/3a5d6291fb25c165. Report an issue: GitHub.