The Next.js 15 Async Params Pitfall: Why 50% of Projects Break on One Missing await
By Arash Latifi
In Next.js 15 params and searchParams are Promises. Without await the page breaks or goes stale. The correct pattern, common mistakes, and a 30-second fix.
TL;DR: In Next.js 15
paramsandsearchParamsare not plain objects — they arePromises. If you don'tawaitthem, the build breaks or you get stale/undefined values. Seen in 50% of migrations, fixed with a 3-line pattern.
Why async?
Until Next.js 14 this worked:
// Next.js 14 — worked
export default function Page({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>;
}
In Next.js 15 the same code warns at build and params.slug becomes undefined at runtime. Reason: the team made params a Promise for Partial Prerendering and precise caching — right for performance, breaking for everyone.
If you're still busy with the Tailwind v4 migration or image optimization, fix this trap first.
Wrong vs right
❌ Wrong 1: without await
// ❌ breaks — params is a Promise
export default function Page({ params }: { params: Promise<{ slug: string }> }) {
const slug = params.slug; // undefined
return <h1>{slug}</h1>;
}
✅ Right: await in server component
// ✅ correct — server component is async
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
❌ Wrong 2: generateMetadata without await
❌ Wrong 3: searchParams in client
30-second migration checklist
A quick map of where await is required:
| Location | Type in Next.js 15 | await needed? |
|---|---|---|
| page.tsx / layout.tsx (server) | Promise | Yes |
| generateMetadata | Promise | Yes |
| Route Handler (route.ts) | Promise | Yes |
| useParams() in a client component | Already resolved | No |
| generateStaticParams | Takes no input | No |
| params typed as any | Error stays hidden | Fix the type |
Final pattern for every page.tsx / layout.tsx:
Post-migration checklist:
- [ ] Ran the official
next-async-request-apicodemod across the project - [ ] No
anytypes left onparamsorsearchParams - [ ] Both
generateMetadataandgenerateViewportawaittheir inputs - [ ] Dynamic routes exercised once on a real deployment, not just
dev - [ ] The "Cannot access Request information synchronously" error never appeared
When you don't need await
- Client component with
useParams(): TheuseParams()hook fromnext/navigationgives you the resolved value — no await needed. This trap is only for server props. generateStaticParams: It creates and returnsparams— no input.
FAQ
Q: Do I need to make everything async?
Only files that receive params/searchParams. Others can stay sync. But every Page/Layout that takes these props must be async.
Q: Why doesn't TypeScript complain?
If you typed params as any, the error is hidden. Type it as Promise<{ slug: string }> to catch it in tsc.
Q: Does this conflict with Zod form validation?
No — Zod lives in a Server Action. Only if you read params inside that Action do you also need await.
Wrap-up
Next.js 15 added one await to every params to make caching and PPR work. If you see a white page or undefined after upgrading, check this file first. One await saves the whole site.
Sources
- How to upgrade to version 15 — the official explanation of
paramsandsearchParamsbecoming aPromise - Next.js codemods docs — the
next-async-request-apicodemod used above - Cannot access Request information synchronously — reference for the most common post-migration error
- Stack Overflow: params should be awaited — a real Route Handler case
- Stack Overflow: Promise-based params in a prop expression — the type error when you forget in the wrong spot