Overview

Next.js 15 turned a set of request-scoped APIs into Promises. params, searchParams, cookies(), headers(), and draftMode() are now async and must be awaited before you read their values. The Next.js 14 synchronous access pattern (params.slug, cookies().get(...)) produces a “should be awaited” error or an undefined value in 15. This change lets Next.js start rendering before request data resolves, which improves streaming. This page is the reference for the breaking change so the sync pattern does not recur across pages. The App Router conventions live in nextjs-app-router; the caching interactions live in nextjs-caching.

Await params and searchParams in pages and metadata

Page, layout, and generateMetadata props that used to be plain objects are now Promises. Await them, and type them as Promise<...>.

// app/posts/[slug]/page.tsx
export default async function Post(
  { params, searchParams }: {
    params: Promise<{ slug: string }>;
    searchParams: Promise<{ ref?: string }>;
  },
) {
  const { slug } = await params;
  const { ref } = await searchParams;
  const post = await getPost(slug);
  return <Article post={post} referrer={ref} />;
}

The same applies to generateMetadata: await params before reading slug. A page and its generateMetadata both receive the same Promise, so await it in each independently.

Await cookies(), headers(), and draftMode()

The request helpers from next/headers are async in 15. Await the call, then use the returned store.

import { cookies, headers, draftMode } from "next/headers";
 
export default async function Account() {
  const cookieStore = await cookies();
  const session = cookieStore.get("session")?.value;
 
  const headerList = await headers();
  const locale = headerList.get("accept-language");
 
  const { isEnabled } = await draftMode();
  return <Profile session={session} locale={locale} preview={isEnabled} />;
}

Reading any of these still opts the route segment out of static rendering, exactly as before. See nextjs-caching for how dynamic functions interact with the Full Route Cache.

Run the codemod, then fix what it cannot

Next.js ships a codemod that rewrites most call sites automatically.

npx @next/codemod@latest next-async-request-api .

The codemod handles the mechanical cases: it awaits the APIs and updates the prop types. It cannot always infer intent in client components or in helpers that pass params through several layers. After running it, search for any remaining synchronous reads and the temporary (await ...) wrappers it inserts, and clean them into proper await bindings.

Watch the client-component and route-handler edges

A few spots the breaking change touches that are easy to miss:

  • Client components cannot await. Read params/searchParams in a server component and pass the resolved values down as props, or use the use() hook to unwrap the Promise inside a client component.
  • Route handlers (route.ts) receive params as a Promise in the second argument; await it before use. See nextjs-route-handlers.
  • Helpers that accept params should accept the resolved object, not the Promise. Await at the route boundary and pass plain values inward, so the async surface stays at the edge.

Keep the async boundary at the route entry

Treat the awaited request data as something you resolve once, at the top of the route, and then thread downward as plain values. Do not pass the Promise into deep helper functions; that spreads await and the dynamic-rendering opt-out across the tree. Resolving at the entry keeps server components below it simple and keeps the RSC tree easy to reason about.