| 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. |
| allowed-tools | Read, Write, Edit, Glob, Grep, TodoWrite |
| version | 1.0.0 |
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 Promiseheaders()returns PromisedraftMode()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