Articles /
Next.js 16 Async Params: Fix Route Handlers, Pages and generateMetadata
Next.js 16 async params explained: fix pages, layouts, route handlers and generateMetadata, plus the codemod and the cookies() and headers() change.
Next.js 16 async params is the breaking change to check first when you move from 15 to 16. Next.js 15 made params, searchParams, cookies() and headers() asynchronous but still allowed synchronous access during a compatibility period. Next.js 16 removes that. I hit this while porting the blog system on my portfolio to Next.js 16. Below is the fixed version of every common call site, the codemod, and what the codemod does not convert.
The short answer
Await them. In Next.js 16, params and searchParams are Promises, and cookies(), headers() and draftMode() return Promises.
const { slug } = await params;
const { page } = await searchParams;
const token = (await cookies()).get("token");
Then run npx @next/codemod@canary next-async-request-api . and search your code for @next-codemod-error. Everything below targets Next.js 16 (App Router). On 15, synchronous access still works with a warning. On 14 it is still synchronous.
What changed, exactly
According to the Next.js upgrade guide, version 15 introduced the Async Request APIs with temporary synchronous compatibility, and Next.js 16 fully removes synchronous access. The affected APIs are:
paramsandsearchParamsinpage,layout,routeanddefaultfilesparamsandsearchParamsingenerateMetadataandgenerateViewportcookies(),headers()anddraftMode()fromnext/headers
Route handlers
The second argument of a dynamic route handler carries params as a Promise.
// app/api/posts/[slug]/route.ts (Next.js 16, App Router)
export async function GET(
_req: Request,
ctx: { params: Promise<{ slug: string }> },
) {
const { slug } = await ctx.params;
return Response.json({ slug });
}
Pages and generateMetadata
generateMetadata and the page component both receive the same Promise-typed params, so both must await it.
// app/blog/[slug]/page.tsx (Next.js 16)
import type { Metadata } from "next";
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
return {
title: slug, // look up the post by slug here
alternates: { canonical: `https://najmulhasan.dev/blog/${slug}` },
};
}
export default async function Page({ params }: Props) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
searchParams, cookies() and headers()
The same rule applies to searchParams and to the functions from next/headers.
// app/search/page.tsx (Next.js 16)
import { cookies, headers } from "next/headers";
type Props = { searchParams: Promise<{ page?: string }> };
export default async function Page({ searchParams }: Props) {
const { page = "1" } = await searchParams;
const theme = (await cookies()).get("theme")?.value ?? "dark";
const userAgent = (await headers()).get("user-agent");
return <p>{`page ${page}, theme ${theme}, ${userAgent}`}</p>;
}
Client component pages
A client component cannot be async. The codemod docs show React.use() for this case.
// app/blog/[slug]/page.tsx, if the page itself is a client component
"use client";
import { use } from "react";
export default function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = use(params);
return <h1>{slug}</h1>;
}
Run the codemod, then check what it missed
The Next.js docs list this codemod for the async request APIs:
npx @next/codemod@canary next-async-request-api .
It rewrites cookies(), headers() and draftMode() calls to be awaited or wrapped with React.use(). It also rewrites property access on params and searchParams in page, layout, route and default files and in generateMetadata and generateViewport. Where it cannot convert a call site, it leaves the marker @next-codemod-error. Search for that string and fix those spots by hand.
Pitfalls
- Helpers are not made async for you. The Next.js error docs say the codemod will not turn a synchronous helper that calls
cookies()into an async one. Make the helperasyncandawaitit at every caller. - Third-party code. A library that reads request data synchronously inside will break. The docs suggest checking whether it has a release compatible with Next.js 15 or later.
- Image routes. In Next.js 16, the image-generating function (such as
opengraph-image) receivesparamsandidas Promises.generateImageMetadatastill receives synchronousparams. - I have not run this exact code. I tested the call sites while porting my own blog. The snippets here are written fresh for this article.
Summary
- Next.js 15 warned about synchronous
params,searchParams,cookies()andheaders(). Next.js 16 removes the synchronous path. awaitthem in pages, layouts, route handlers andgenerateMetadata. Useuse()in client components.- Run
npx @next/codemod@canary next-async-request-api ., then search for@next-codemod-error. - Sync helpers and third-party libraries are the usual leftovers.
Next: send and verify email OTP codes with route handlers and SSR cookies: Supabase Email OTP in Next.js.