Claude-skill-registry-data migrating-async-request-apis
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.
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/migrating-async-request-apis" ~/.claude/skills/majiayu000-claude-skill-registry-data-migrating-async-request-apis && rm -rf "$T"
data/migrating-async-request-apis/SKILL.mdMIGRATION: 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:
prop is now a Promiseparams - Layouts:
prop is now a Promiseparams - Route Handlers:
argument is now a Promiseparams
- Pages:
-
Search Parameters (
)searchParams- Page
prop is now a PromisesearchParams
- Page
-
Request APIs
returns Promisecookies()
returns Promiseheaders()
returns PromisedraftMode()
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
for Promise type handling@typescript/TYPES-type-guards
Migration Checklist
When migrating to async request APIs:
- Update all
props toparams
typePromise<T> - Update all
props tosearchParams
typePromise<T> - Add
to allawait
callscookies() - Add
to allawait
callsheaders() - Add
to allawait
callsdraftMode() - 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