MIGRATION: Async Request APIs
Purpose
Teach the breaking changes in Next.js 16 where request APIs are now async. This affects params, searchParams, cookies(), headers(), and draftMode() - all now return Promises and require await.
When to Use
- •Migrating from Next.js 15 to 16
- •Fixing TypeScript errors about Promise types
- •Working with route parameters, search params, or request headers
- •Encountering "object is possibly undefined" errors
- •Updating Server Components or API routes
Breaking Changes Overview
APIs Now Async
- •
Route Parameters (
params)- •Pages:
paramsprop is now a Promise - •Layouts:
paramsprop is now a Promise - •Route Handlers:
paramsargument is now a Promise
- •Pages:
- •
Search Parameters (
searchParams)- •Page
searchParamsprop is now a Promise
- •Page
- •
Request APIs
- •
cookies()returns Promise - •
headers()returns Promise - •
draftMode()returns Promise
- •
Type Changes
import { cookies, headers, draftMode } from 'next/headers';
type CookiesReturn = Promise<ReadonlyRequestCookies>;
type HeadersReturn = Promise<ReadonlyHeaders>;
type DraftModeReturn = Promise<{ isEnabled: boolean }>;
Migration Patterns
Pattern 1: Page Route Params
Before (Next.js 15):
export default function Page({ params }: { params: { id: string } }) {
return <div>User ID: {params.id}</div>;
}
export async function generateMetadata({ params }: { params: { id: string } }) {
return { title: `User ${params.id}` };
}
After (Next.js 16):
export default async function Page({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
return <div>User ID: {id}</div>;
}
export async function generateMetadata({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
return { title: `User ${id}` };
}
Pattern 2: Search Parameters
Before (Next.js 15):
export default function SearchPage({
searchParams
}: {
searchParams: { q?: string; page?: string }
}) {
const query = searchParams.q || '';
const page = Number(searchParams.page) || 1;
return <SearchResults query={query} page={page} />;
}
After (Next.js 16):
export default async function SearchPage({
searchParams
}: {
searchParams: Promise<{ q?: string; page?: string }>
}) {
const params = await searchParams;
const query = params.q || '';
const page = Number(params.page) || 1;
return <SearchResults query={query} page={page} />;
}
Pattern 3: Cookies API
Before (Next.js 15):
import { cookies } from 'next/headers';
export default function Component() {
const cookieStore = cookies();
const token = cookieStore.get('token');
return <div>Token: {token?.value}</div>;
}
After (Next.js 16):
import { cookies } from 'next/headers';
export default async function Component() {
const cookieStore = await cookies();
const token = cookieStore.get('token');
return <div>Token: {token?.value}</div>;
}
Pattern 4: Headers API
Before (Next.js 15):
import { headers } from 'next/headers';
export default function Component() {
const headersList = headers();
const userAgent = headersList.get('user-agent');
return <div>User Agent: {userAgent}</div>;
}
After (Next.js 16):
import { headers } from 'next/headers';
export default async function Component() {
const headersList = await headers();
const userAgent = headersList.get('user-agent');
return <div>User Agent: {userAgent}</div>;
}
Pattern 5: Draft Mode
Before (Next.js 15):
import { draftMode } from 'next/headers';
export default function Component() {
const { isEnabled } = draftMode();
return <div>Draft mode: {isEnabled ? 'on' : 'off'}</div>;
}
After (Next.js 16):
import { draftMode } from 'next/headers';
export default async function Component() {
const { isEnabled } = await draftMode();
return <div>Draft mode: {isEnabled ? 'on' : 'off'}</div>;
}
Pattern 6: Route Handlers
Before (Next.js 15):
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const headersList = headers();
const auth = headersList.get('authorization');
return Response.json({ id: params.id, auth });
}
After (Next.js 16):
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const [{ id }, headersList] = await Promise.all([
params,
headers()
]);
const auth = headersList.get('authorization');
return Response.json({ id, auth });
}
Pattern 7: Layouts with Params
Before (Next.js 15):
export default function Layout({
children,
params
}: {
children: React.ReactNode;
params: { locale: string };
}) {
return (
<html lang={params.locale}>
<body>{children}</body>
</html>
);
}
After (Next.js 16):
export default async function Layout({
children,
params
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}
Common Migration Errors
Error 1: Missing await
const { id } = params;
Fix:
const { id } = await params;
Error 2: Non-async function
export default function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
}
Fix:
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
}
Error 3: Wrong type annotation
export default async function Page({ params }: { params: { id: string } }) {
const { id } = await params;
}
Fix:
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
}
Error 4: Accessing properties directly
const cookieStore = await cookies(); const allCookies = cookieStore.getAll();
Fix (Same, but ensure await on cookies()):
const cookieStore = await cookies(); const allCookies = cookieStore.getAll();
Performance Optimization
Use Promise.all for Multiple Async Calls
Inefficient (Sequential):
const { id } = await params;
const search = await searchParams;
const cookieStore = await cookies();
const headersList = await headers();
Optimized (Parallel):
const [{ id }, search, cookieStore, headersList] = await Promise.all([
params,
searchParams,
cookies(),
headers()
]);
When to Use Sequential vs Parallel
Sequential (dependencies exist):
const { id } = await params;
const data = await fetchData(id);
Parallel (no dependencies):
const [{ id }, cookieStore] = await Promise.all([
params,
cookies()
]);
Type Safety
Define Reusable Types
type PageParams<T = {}> = Promise<T>;
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>;
type PageProps<T = {}> = {
params: PageParams<T>;
searchParams: SearchParams;
};
export default async function Page({ params, searchParams }: PageProps<{ id: string }>) {
const { id } = await params;
const search = await searchParams;
return <div>{id}</div>;
}
Type Guards with Promises
For advanced Promise type handling and type guards, see @typescript/TYPES-type-guards skill.
function isValidId(id: string): id is string {
return /^[a-z0-9]+$/i.test(id);
}
export default async function Page({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
if (!isValidId(id)) {
return <div>Invalid ID</div>;
}
return <div>Valid ID: {id}</div>;
}
Special Cases
Multi-Segment Routes
export default async function Page({
params
}: {
params: Promise<{ category: string; product: string }>
}) {
const { category, product } = await params;
return (
<div>
<h1>Category: {category}</h1>
<h2>Product: {product}</h2>
</div>
);
}
Catch-All Routes
export default async function Page({
params
}: {
params: Promise<{ slug: string[] }>
}) {
const { slug } = await params;
const path = slug.join('/');
return <div>Path: {path}</div>;
}
Optional Catch-All Routes
export default async function Page({
params
}: {
params: Promise<{ slug?: string[] }>
}) {
const { slug } = await params;
if (!slug) {
return <div>Home Page</div>;
}
return <div>Path: {slug.join('/')}</div>;
}
References
For detailed migration examples, edge cases, and troubleshooting, see:
- •Detailed Async Patterns Reference
- •Next.js 16 Migration Guide: https://nextjs.org/docs/app/building-your-application/upgrading/version-16
- •
@typescript/TYPES-type-guardsfor Promise type handling
Migration Checklist
When migrating to async request APIs:
- • Update all
paramsprops toPromise<T>type - • Update all
searchParamsprops toPromise<T>type - • Add
awaitto allcookies()calls - • Add
awaitto allheaders()calls - • Add
awaitto alldraftMode()calls - • Convert components to async where needed
- • Update type annotations
- • Use Promise.all for multiple async calls
- • Test all dynamic routes
- • Test all API routes
- • Verify TypeScript compilation
- • Run production build to catch errors
Success Criteria
- •No TypeScript errors related to Promise types
- •All dynamic routes work correctly
- •All API routes handle params properly
- •Cookies, headers, and draft mode work as expected
- •No runtime errors about "object is possibly undefined"
- •Build completes successfully