Fix Next.js "params should be awaited" Error in Next.js 15+
If you are seeing the params should be awaited Next.js error after upgrading to Next.js 15 or following an older App Router tutorial, you are not alone.
The error usually looks something like this:
Route "/blog/[slug]" used params.slug. params should be awaited before using its properties.
Sometimes it appears with searchParams.
Sometimes it appears with cookies() or headers().
And sometimes the page still seems to work, but your terminal keeps shouting at you.
This article will slow it down and explain the fix in a beginner-friendly way.
No deep framework lecture first. Just the actual problem, the broken code, the fixed code, and the reason it works.
What This Error Means in Plain English
In older Next.js code, you may have treated params like a normal JavaScript object.
Something like this:
const slug = params.slug;
That used to feel natural.
If your route was:
/blog/[slug]
and the user opened:
/blog/my-first-post
you expected:
params.slug; // "my-first-post"
In newer Next.js versions, especially Next.js 15+, some request-based values became asynchronous. That means you should treat them like values that need to be waited for before you read from them.
So instead of reading params.slug directly, you do this:
const { slug } = await params;
That is the heart of the fix.
The error is not saying your route is missing.
It is not saying your [slug] folder is wrong.
It is saying:
You are trying to read route data before awaiting it.
The common flow: the page loads, the code reads params.slug directly, Next.js expects params to be awaited, and the error appears.
Why This Changed
Next.js has a group of features called Dynamic APIs.
That sounds more complicated than it is.
In simple terms, Dynamic APIs are values that depend on the current request.
For example:
- What route did the user open?
- What query string is in the URL?
- What cookies came with this request?
- What headers came with this request?
- Is draft mode enabled?
Those values are not the same for every user.
They depend on the incoming request, so Next.js treats them differently from static code.
In Next.js 15+, these Dynamic APIs are asynchronous:
paramssearchParamscookies()headers()draftMode()
That is why old examples from Next.js 13 or 14 can suddenly throw warnings or errors after an upgrade.
The same async idea applies to more than just params. If it depends on the request, check whether it needs await.
The Broken Dynamic Route Example
Let's start with the most common case: a blog post page.
You have this route:
app/blog/[slug]/page.js
The [slug] part means the route is dynamic.
So these URLs can all use the same file:
/blog/nextjs-routing
/blog/react-state
/blog/my-first-post
In older tutorials, you may see code like this:
export default function BlogPostPage({ params }) {
const slug = params.slug;
return <h1>Blog post: {slug}</h1>;
}
This is the broken pattern in Next.js 15+.
The problem is this line:
const slug = params.slug;
You are reading slug directly from params.
But Next.js expects you to wait for params first.
The Fixed Dynamic Route Example
Here is the fixed version:
export default async function BlogPostPage({ params }) {
const { slug } = await params;
return <h1>Blog post: {slug}</h1>;
}
Two things changed:
- The component became
async. - We used
await paramsbefore readingslug.
That is it.
Small code change, big difference.
Before: read params.slug directly. After: make the function async, await params, then read slug.
Why Adding async Matters
This part is important for beginners.
You cannot use await inside a normal function.
This will not work:
export default function BlogPostPage({ params }) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
JavaScript will complain because await needs an async function.
So you need this:
export default async function BlogPostPage({ params }) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
Think of async as telling JavaScript:
This function may need to wait for something.
And await means:
Wait here until this value is ready, then continue.
So the pattern is:
async function Page({ params }) {
const { slug } = await params;
}
Do Not Await params.slug
This is a very common mistake.
You might try this:
export default async function BlogPostPage({ params }) {
const slug = await params.slug;
return <h1>{slug}</h1>;
}
That looks close, but it is not the right fix.
You are still trying to access .slug before awaiting params.
The correct order is:
const { slug } = await params;
Wait for the whole params object first.
Then read slug.
Fixing generateMetadata
This error often appears on blog sites because the page is not the only place where you read params.
You may also use params inside generateMetadata.
For example, this is broken:
export function generateMetadata({ params }) {
const slug = params.slug;
return {
title: `Post: ${slug}`,
};
}
The fixed version is:
export async function generateMetadata({ params }) {
const { slug } = await params;
return {
title: `Post: ${slug}`,
};
}
This matters because a lot of developers fix page.js, refresh the app, and still see the same warning.
Then they feel stuck.
The reason is simple:
The same broken
params.slugaccess may also exist ingenerateMetadata.
So when you fix a dynamic route, search the whole file for:
params.
Do not only check the page component.
Fixing searchParams
Now let's talk about searchParams.
params usually comes from the path.
Example:
/blog/[slug]
searchParams comes from the query string.
Example:
/products?page=2&sort=new
In older code, you might write:
export default function ProductsPage({ searchParams }) {
const page = searchParams.page || "1";
return <p>Page: {page}</p>;
}
In Next.js 15+, use the async pattern:
export default async function ProductsPage({ searchParams }) {
const { page = "1", sort = "new" } = await searchParams;
return (
<div>
<p>Page: {page}</p>
<p>Sort: {sort}</p>
</div>
);
}
The idea is the same.
Do not read from searchParams first.
Await it first.
Then read from it.
Fixing Both params and searchParams Together
Sometimes a page needs both.
Example:
/category/shoes?page=2
Your route might be:
app/category/[slug]/page.js
Here is the fixed version:
export default async function CategoryPage({ params, searchParams }) {
const { slug } = await params;
const { page = "1" } = await searchParams;
return (
<main>
<h1>Category: {slug}</h1>
<p>Current page: {page}</p>
</main>
);
}
You can await them separately.
That keeps the code easy to read.
Fixing cookies()
The same migration affects cookies() from next/headers.
Here is the broken version:
import { cookies } from "next/headers";
export default function DashboardPage() {
const token = cookies().get("token");
return <pre>{JSON.stringify(token, null, 2)}</pre>;
}
The fixed version:
import { cookies } from "next/headers";
export default async function DashboardPage() {
const cookieStore = await cookies();
const token = cookieStore.get("token");
return <pre>{JSON.stringify(token, null, 2)}</pre>;
}
Again, the order matters.
Do not do this:
const token = await cookies().get("token");
Do this:
const cookieStore = await cookies();
const token = cookieStore.get("token");
You wait for the cookie store first.
Then you read from it.
Fixing headers()
headers() follows the same idea.
Broken:
import { headers } from "next/headers";
export default function Page() {
const userAgent = headers().get("user-agent");
return <p>{userAgent}</p>;
}
Fixed:
import { headers } from "next/headers";
export default async function Page() {
const headerStore = await headers();
const userAgent = headerStore.get("user-agent");
return <p>{userAgent}</p>;
}
If you remember the cookie example, this should feel familiar.
Await the store.
Then read from the store.
Route Handler Example
This error can also show up in route handlers.
For example:
app/api/users/[id]/route.js
Broken:
export async function GET(request, { params }) {
const id = params.id;
return Response.json({
userId: id,
});
}
Fixed:
export async function GET(request, { params }) {
const { id } = await params;
return Response.json({
userId: id,
});
}
The file is different, but the fix is the same.
If the handler receives params, await it before reading id, slug, or any other dynamic value.
TypeScript Version
If you use TypeScript, update the type too.
Broken:
type PageProps = {
params: {
slug: string;
};
};
export default function BlogPostPage({ params }: PageProps) {
return <h1>{params.slug}</h1>;
}
Fixed:
type PageProps = {
params: Promise<{
slug: string;
}>;
};
export default async function BlogPostPage({ params }: PageProps) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
This is where many build errors come from.
The code may look logically correct, but the type still says params is a plain object.
In Next.js 15+, type it like a Promise when you are using this async pattern.
The Codemod Can Help
Next.js provides a codemod for this migration.
A codemod is a script that updates code automatically.
You can run:
npx @next/codemod@latest next-async-request-api .
This can update many params, searchParams, cookies(), and headers() usages for you.
But do not run it and assume everything is perfect.
After the codemod, review the changed files.
Pay extra attention to:
- custom TypeScript types
- helper functions
- auth utilities
- Supabase or other server clients
generateMetadata- route handlers
The codemod is useful, but your app still needs a human review.
Common Mistakes
Mistake 1: Awaiting the Property Instead of params
Avoid this:
const slug = await params.slug;
Use this:
const { slug } = await params;
Await the object first.
Then read the property.
Mistake 2: Forgetting async
Avoid this:
export default function Page({ params }) {
const { slug } = await params;
}
Use this:
export default async function Page({ params }) {
const { slug } = await params;
}
If there is await, the function needs async.
Mistake 3: Fixing the Page but Forgetting Metadata
You may fix this:
export default async function Page({ params }) {
const { slug } = await params;
}
But forget this:
export function generateMetadata({ params }) {
const title = params.slug;
}
If the warning still appears, search the file for:
params.
Mistake 4: Copying Old Tutorial Code
This one is common.
You follow a tutorial.
The tutorial uses:
params.slug
Your app uses Next.js 15 or newer.
Now you get an error.
That does not mean the tutorial is completely useless. It may just be written for an older version of Next.js.
Update the request-data parts to the newer async pattern.
Mistake 5: Thinking the Route Folder Is Wrong
When beginners see this error, they sometimes rename folders or move files around.
Usually, that is not needed.
If your route is:
app/blog/[slug]/page.js
that folder structure is fine.
The issue is usually inside the code:
params.slug
not the route folder itself.
Beginner Debugging Checklist
Use this when the error will not go away.
A quick checklist for finding the common places where this error hides.
Ask these questions:
- Is this a dynamic route like
[slug],[id], or[category]? - Am I reading
params.slug,params.id, or another property directly? - Did I make the function
async? - Did I use
const { slug } = await params? - Did I also check
generateMetadata? - Am I using
searchParams? - Am I using
cookies()orheaders()? - Do my TypeScript types say
paramsis a Promise? - Did I run the codemod and review the result?
Most of the time, one of these checks will reveal the issue.
Quick Fix Cheatsheet
Here are the patterns to remember.
For params:
const { slug } = await params;
For searchParams:
const { page = "1" } = await searchParams;
For cookies():
const cookieStore = await cookies();
const token = cookieStore.get("token");
For headers():
const headerStore = await headers();
const userAgent = headerStore.get("user-agent");
For TypeScript:
type PageProps = {
params: Promise<{ slug: string }>;
};
How This Relates to Other Next.js Bugs
This error is part of a bigger pattern in Next.js.
Some bugs happen because code runs in a different place than you expected.
For example, in my previous article on fixing Next.js hydration errors with localStorage, the problem was server HTML not matching the first client render:
Server render and browser render did not match.
In the middleware redirect loop article, the problem was request flow:
/dashboard redirects to /login
/login also triggers middleware
the app loops
This article is also about flow.
But this time, the flow is about when request data is ready:
Wait for request data
then read from it
Once you see it that way, the error becomes much less scary.
Final Takeaway
The Next.js params should be awaited error usually means you are reading request-based data too early.
In older code, this looked normal:
const slug = params.slug;
In Next.js 15+, use this:
const { slug } = await params;
And remember the simple rule:
Treat route and request data as async. Await it first, then read from it.
That rule applies to:
paramssearchParamscookies()headers()draftMode()
If you are upgrading a project, run the codemod, then review your dynamic routes, metadata functions, route handlers, and TypeScript types.
I write practical debugging guides and build full-stack products. You can find more of my work at amrishkhan.dev.
Suggested Internal Links
- Previous article: Fix Next.js Hydration Error with localStorage
References
- Next.js docs: Dynamic APIs are Asynchronous
- Next.js docs: Upgrading to Version 15
- Next.js docs: cookies
- Next.js docs: Dynamic Segments
Top comments (0)