params and searchParams

Both Are Promises

Since Next.js 15, params and searchParams are Promises. Await them in async Server Components (or use React's use() in Client Components). Accessing them synchronously was removed in Next.js 16.

app/products/page.tsx for /products?category=books&page=2:

type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>;

export default async function ProductsPage({ searchParams }: { searchParams: SearchParams }) {
  const { category = 'all', page = '1' } = await searchParams;
  return (
    <p>
      Category: {String(category)}, page {Number(page)}
    </p>
  );
}

Note: Reading searchParams makes the page dynamic (rendered per request), because the value is only known when the request arrives.

Typed Helpers: PageProps

Next.js generates global helper types while you run next dev or next build. PageProps<'/products/[id]'> knows the exact params of that route:

export default async function Page(props: PageProps<'/products/[id]'>) {
  const { id } = await props.params; // id: string
  const query = await props.searchParams;
  return <h1>Product {id} {query.ref ? '(from ' + query.ref + ')' : ''}</h1>;
}

In a Client Component

'use client';

import { use } from 'react';

export default function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = use(params); // unwrap the Promise with React 19's use()
  return <p>Client page for {id}</p>;
}

Practice

Exercise: Write app/orders/[orderId]/page.tsx that shows "Order ORD-1001" for /orders/ORD-1001 and also shows ?tab=invoice if present.

Show answer
export default async function OrderPage({
  params,
  searchParams,
}: {
  params: Promise<{ orderId: string }>;
  searchParams: Promise<{ tab?: string }>;
}) {
  const { orderId } = await params;
  const { tab = 'summary' } = await searchParams;
  return (
    <main>
      <h1>Order {orderId}</h1>
      <p>Tab: {tab}</p>
    </main>
  );
}