DEV Community

Cover image for Fix Next.js "params should be awaited" Error in Next.js 15+
Amrishkhan Sheik Abdullah
Amrishkhan Sheik Abdullah

Posted on

Fix Next.js "params should be awaited" Error in Next.js 15+

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.
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

That used to feel natural.

If your route was:

/blog/[slug]
Enter fullscreen mode Exit fullscreen mode

and the user opened:

/blog/my-first-post
Enter fullscreen mode Exit fullscreen mode

you expected:

params.slug; // "my-first-post"
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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:

  • params
  • searchParams
  • cookies()
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

In older tutorials, you may see code like this:

export default function BlogPostPage({ params }) {
  const slug = params.slug;

  return <h1>Blog post: {slug}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

This is the broken pattern in Next.js 15+.

The problem is this line:

const slug = params.slug;
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

Two things changed:

  1. The component became async.
  2. We used await params before reading slug.

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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}`,
  };
}
Enter fullscreen mode Exit fullscreen mode

The fixed version is:

export async function generateMetadata({ params }) {
  const { slug } = await params;

  return {
    title: `Post: ${slug}`,
  };
}
Enter fullscreen mode Exit fullscreen mode

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.slug access may also exist in generateMetadata.

So when you fix a dynamic route, search the whole file for:

params.
Enter fullscreen mode Exit fullscreen mode

Do not only check the page component.

Fixing searchParams

Now let's talk about searchParams.

params usually comes from the path.

Example:

/blog/[slug]
Enter fullscreen mode Exit fullscreen mode

searchParams comes from the query string.

Example:

/products?page=2&sort=new
Enter fullscreen mode Exit fullscreen mode

In older code, you might write:

export default function ProductsPage({ searchParams }) {
  const page = searchParams.page || "1";

  return <p>Page: {page}</p>;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Your route might be:

app/category/[slug]/page.js
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

Again, the order matters.

Do not do this:

const token = await cookies().get("token");
Enter fullscreen mode Exit fullscreen mode

Do this:

const cookieStore = await cookies();
const token = cookieStore.get("token");
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Broken:

export async function GET(request, { params }) {
  const id = params.id;

  return Response.json({
    userId: id,
  });
}
Enter fullscreen mode Exit fullscreen mode

Fixed:

export async function GET(request, { params }) {
  const { id } = await params;

  return Response.json({
    userId: id,
  });
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

Fixed:

type PageProps = {
  params: Promise<{
    slug: string;
  }>;
};

export default async function BlogPostPage({ params }: PageProps) {
  const { slug } = await params;

  return <h1>{slug}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Use this:

const { slug } = await params;
Enter fullscreen mode Exit fullscreen mode

Await the object first.

Then read the property.

Mistake 2: Forgetting async

Avoid this:

export default function Page({ params }) {
  const { slug } = await params;
}
Enter fullscreen mode Exit fullscreen mode

Use this:

export default async function Page({ params }) {
  const { slug } = await params;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

But forget this:

export function generateMetadata({ params }) {
  const title = params.slug;
}
Enter fullscreen mode Exit fullscreen mode

If the warning still appears, search the file for:

params.
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Copying Old Tutorial Code

This one is common.

You follow a tutorial.

The tutorial uses:

params.slug
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

that folder structure is fine.

The issue is usually inside the code:

params.slug
Enter fullscreen mode Exit fullscreen mode

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:

  1. Is this a dynamic route like [slug], [id], or [category]?
  2. Am I reading params.slug, params.id, or another property directly?
  3. Did I make the function async?
  4. Did I use const { slug } = await params?
  5. Did I also check generateMetadata?
  6. Am I using searchParams?
  7. Am I using cookies() or headers()?
  8. Do my TypeScript types say params is a Promise?
  9. 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;
Enter fullscreen mode Exit fullscreen mode

For searchParams:

const { page = "1" } = await searchParams;
Enter fullscreen mode Exit fullscreen mode

For cookies():

const cookieStore = await cookies();
const token = cookieStore.get("token");
Enter fullscreen mode Exit fullscreen mode

For headers():

const headerStore = await headers();
const userAgent = headerStore.get("user-agent");
Enter fullscreen mode Exit fullscreen mode

For TypeScript:

type PageProps = {
  params: Promise<{ slug: string }>;
};
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

In the middleware redirect loop article, the problem was request flow:

/dashboard redirects to /login
/login also triggers middleware
the app loops
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

In Next.js 15+, use this:

const { slug } = await params;
Enter fullscreen mode Exit fullscreen mode

And remember the simple rule:

Treat route and request data as async. Await it first, then read from it.

That rule applies to:

  • params
  • searchParams
  • cookies()
  • 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

References

Top comments (0)