DEV Community

Najmul Hasan
Najmul Hasan

Posted on Originally published at najmulhasan.dev

Next.js 16 Async Params: Fix Route Handlers, Pages and generateMetadata

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");
Enter fullscreen mode Exit fullscreen mode

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:

  • params and searchParams in page, layout, route and default files
  • params and searchParams in generateMetadata and generateViewport
  • cookies(), headers() and draftMode() from next/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 });
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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 helper async and await it 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) receives params and id as Promises. generateImageMetadata still receives synchronous params.
  • 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() and headers(). Next.js 16 removes the synchronous path.
  • await them in pages, layouts, route handlers and generateMetadata. Use use() 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.


Originally published at najmulhasan.dev/blog/nextjs-16-async-params.

Top comments (0)