Stop Next.js 16 Crashes: Fixing `searchParams` Errors
Learn to normalize `searchParams` in Next.js 15 and avoid runtime crashes with our step-by-step guide.

⚡ Next.js Implementation Guides
In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
I was working on a Next.js 16 project, happily paging through admin lists, when Vercel started screaming: “TypeError: Cannot read properties of undefined (reading '0')” right inside .next/server/app/(admin)/admin/dobavnice/page.js. React 19 and Turbopack already switched searchParams from the old plain object into a ReadonlyURLSearchParams, so every params.foo?.[0] in my server components fell apart at runtime. This guide shows how to normalize the new shape once, reuse it everywhere, and stop the cold-start crashes on Vercel.
Step 1: Reproduce the failure and understand the new shape
Hit any page that still does Array.isArray(params.foo) on Next.js 15 and you’ll see the same stack trace:
TypeError: Cannot read properties of undefined (reading '0')
at async m (.next/server/app/(admin)/admin/dobavnice/page.js:1:9228)
Server components now receive a promise that resolves to a ReadonlyURLSearchParams. Missing keys return undefined, so params.page[0] blows up. The fix is to read search params through a helper that understands every shape Next has ever returned: plain records, URLSearchParams, or nothing at all.
Step 2: Add a helper that normalizes searchParams
Create or update src/lib/utils.ts with a helper that unwraps the key cleanly.
// File: src/lib/utils.ts
import type { ReadonlyURLSearchParams } from "next/navigation"
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export type SearchParamsLike =
| ReadonlyURLSearchParams
| URLSearchParams
| Record<string, string | string[] | undefined>
| null
| undefined
export const getSearchParamValues = (
params: SearchParamsLike,
key: string
): string[] => {
if (!params) return []
if (typeof (params as URLSearchParams).getAll === "function") {
const values = (params as URLSearchParams).getAll(key)
return values.filter((value): value is string => typeof value === "string")
}
const record = params as Record<string, string | string[] | undefined>
const raw = record?.[key]
if (Array.isArray(raw)) {
return raw.filter((value): value is string => typeof value === "string")
}
return typeof raw === "string" ? [raw] : []
}
export const getSearchParamValue = (
params: SearchParamsLike,
key: string
): string | undefined => {
const [value] = getSearchParamValues(params, key)
return value
}
The helper checks for URLSearchParams compatibility (getAll), falls back to the legacy record shape, and returns an empty array for nullish input. getSearchParamValue mirrors the old “first element or nothing” behaviour without the brittle [0] access.
Step 3: Update server components to use the helper
Replace the manual Array.isArray logic in affected pages. Example refactor:
// File: src/app/(admin)/admin/dobavnice/page.tsx
type AdminDeliveryNotesListPageProps = Readonly<{
searchParams: Promise<SearchParamsLike>;
}>;
export default async function AdminDeliveryNotesListPage({
searchParams,
}: AdminDeliveryNotesListPageProps) {
const params = await searchParams;
const page = Number.parseInt(getSearchParamValue(params, "page") ?? "1", 10);
const search = getSearchParamValue(params, "search") ?? "";
const statusParam = getSearchParamValue(params, "status");
const status = statusParam ? (statusParam as DeliveryNoteStatus) : undefined;
// ...
}
The rendering stays identical. Swap in the helper, update the prop type to Promise<SearchParamsLike>, and keep the business logic untouched. Repeat for other pages that previously relied on Array.isArray(params.foo).
Step 4: Redeploy and keep your build green
Run pnpm build (or NEXT_DISABLE_TURBOPACK=1 pnpm next build) locally to make sure the helper compiles. Redeploy to Vercel and the TypeError disappears because the runtime no longer dereferences [0] on undefined. Pagination and filters work exactly as before, only now they’re resilient to future Next.js changes.
Wrap-up
Next.js 15 and React 19 changed the shape of searchParams, and the helper is the difference between healthy admin lists and Vercel’s “Cannot read properties of undefined” panic. By centralising the normalization and reusing it across your server components, you keep pagination, filters, and driver dashboards safe from the breaking change—all without rewriting your business logic.
Let me know in the comments if you have questions, and subscribe for more practical development guides.
Thanks, Matija