name migrating-async-request-apis description Teach async request APIs in Next.js 16 - params, searchParams, cookies(), headers(), draftMode() are now async. Use when migrating from Next.js 15, fixing type errors, or working with request data. Use when this capability is needed. metadata {"author":"djankies"}
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: params prop is now a Promise
Layouts: params prop is now a Promise
Route Handlers: params argument is now a Promise
Search Parameters (searchParams)
Page searchParams prop is now a Promise
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:
Migration Checklist
When migrating to async request APIs:
Update all params props to Promise<T> type
Update all searchParams props to Promise<T> type
Add await to all cookies() calls
Add await to all headers() calls
Add await to all draftMode() 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
Converted and distributed by TomeVault — claim your Tome and manage your conversions.