validate route params nextjs app router

Validate Route Params in Next.js App Router with Zod

Validate Next.js App Router params and searchParams with Zod before using dynamic segments, route handlers, redirects, or API queries.

Open Next Route Map

Quick answer: should route params be validated?

Yes. App Router params come from the URL, so treat them as untrusted strings until a schema accepts them. Zod is useful when a dynamic segment must be a UUID, enum, slug, number-like string, or optional catch-all array.

Validate a dynamic segment

A `[id]` segment arrives as a string. Validate it before passing it into a database query, API call, or service function.

import { z } from "zod";

const ParamsSchema = z.object({
  id: z.string().uuid(),
});

export default async function UserPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const parsed = ParamsSchema.parse(await params);

  return <UserProfile id={parsed.id} />;
}

Use safeParse for notFound or redirects

Use `safeParse` when invalid route params should become a controlled 404, redirect, or error response instead of an uncaught exception.

import { notFound } from "next/navigation";

const ProductParamsSchema = z.object({
  sku: z.string().min(3).max(80),
});

export default async function ProductPage({ params }: PageProps) {
  const result = ProductParamsSchema.safeParse(await params);

  if (!result.success) {
    notFound();
  }

  return <ProductDetails sku={result.data.sku} />;
}

Validate searchParams

Search params are often optional and string-based. Use coercion carefully when the URL value should become a number, boolean, enum, or pagination option.

const SearchParamsSchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  sort: z.enum(["newest", "popular"]).default("newest"),
});

export default async function ProductsPage({
  searchParams,
}: {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
  const parsed = SearchParamsSchema.parse(await searchParams);
  return <ProductGrid page={parsed.page} sort={parsed.sort} />;
}

Catch-all route params

Catch-all segments such as `[...slug]` produce arrays. Optional catch-all segments such as `[[...slug]]` can be absent, so model that difference explicitly.

  • Use `z.array(z.string()).min(1)` for required catch-all routes.
  • Use `z.array(z.string()).optional()` for optional catch-all routes.
  • Normalize empty or unsupported path segments before querying content.

Route handler params

Route handlers should validate params before reading request bodies or touching external systems. This keeps bad URLs from reaching deeper service code.

export async function GET(
  request: Request,
  { params }: { params: Promise<{ teamId: string }> },
) {
  const parsed = z.object({ teamId: z.string().uuid() }).safeParse(await params);

  if (!parsed.success) {
    return Response.json({ error: "Invalid team id" }, { status: 400 });
  }

  return Response.json(await getTeam(parsed.data.teamId));
}

Pair validation with a route map

A route map helps identify which dynamic segments need validation. Generate the route inventory first, then add schemas for high-risk params such as IDs, slugs, locales, and catch-all docs paths.

  • List dynamic routes before adding validation coverage.
  • Mark route handlers that need both params and body validation.
  • Use route map output as a QA checklist for invalid param tests.

Production checklist

Before shipping route param validation, test valid params, malformed params, missing catch-all values, and unexpected search param values.

  • Do not trust URL params as already typed.
  • Use `parse` for hard failures and `safeParse` for controlled 404 or 400 responses.
  • Keep schemas close to the route that owns the URL shape.
  • Avoid coercion unless the URL value is intentionally numeric or boolean-like.

Use FrameworkKit to generate the starter code, then review the output before shipping it in production.

Generate with Next Route Map

Next Route Map resources

FAQ

Are Next.js App Router params already typed?

They can be typed in TypeScript, but the runtime values still come from the URL. Validate them when the route needs strict UUID, enum, slug, or numeric behavior.

Should invalid route params return 404 or 400?

Pages often use `notFound()` for invalid params. Route handlers usually return 400 when a client calls an API route with invalid params.

Can Zod validate searchParams?

Yes. Use object schemas for expected query keys and `z.coerce` only when string values should become numbers, booleans, or other typed values.

How does a route map help validation?

A route map lists dynamic segments and route handlers so you can see which params need schemas, tests, and QA coverage.

Related comparisons

Related tools