How to Fix: Vercel build will fail if there’s a `[…catchAll]` route that does not await the params

6 min read

Vercel build fails with a […catchAll] route when params is not awaited: cause, fix, and edge cases

This bug is easy to miss locally and brutal in CI: a Next.js app can appear fine until the Vercel build hits a dynamic catch-all route and fails because the route reads params synchronously instead of awaiting it. If your page lives under app/catch-all/[…catchAll]/page.tsx, the fix is usually small, but the reason matters if you want to avoid similar failures in other dynamic routes.

You can inspect the reproduction in the linked GitHub repository.

Understanding the Root Cause

In the Next.js App Router, route segment data such as params can participate in the framework’s newer async rendering model. With a route like […catchAll], Next.js may treat the route parameters as data that must be resolved within an async server component flow.

The problem appears when code assumes params is immediately available and reads from it synchronously, for example by accessing params.catchAll before the framework expects that value to be resolved. In some environments this may look harmless, but during a production build on Vercel, the compiler and runtime checks are stricter. That mismatch can cause the build to fail.

Why is […catchAll] especially sensitive? A catch-all segment represents a variable-length route match, so Next.js has to normalize and resolve an array of path parts. In the current behavior described by the issue, failing to await params in that route can break the build pipeline because the route contract is treated as asynchronous.

In short, the root cause is this:

  • The page is inside a dynamic catch-all route.
  • The route component accesses params as if it were synchronously available.
  • Next.js/Vercel expects the route data to be consumed in an awaited async context.
  • The build fails when static analysis or runtime preparation detects the invalid access pattern.

This is less about JavaScript syntax and more about how Next.js dynamic route resolution interacts with the server rendering pipeline.

Step-by-Step Solution

The fix is to make the page component explicitly async and await params before reading values from it.

1. Identify the failing route

Look for a file similar to this:

app/catch-all/[...catchAll]/page.tsx

If the page reads params.catchAll directly, that is the first thing to change.

2. Update the page signature

Instead of treating params as a plain object, type it as a Promise and await it inside the page.

type PageProps = {
  params: Promise<{
    catchAll: string[];
  }>;
};

export default async function Page({ params }: PageProps) {
  const { catchAll } = await params;

  return (
    <main>
      <h1>Catch-all route</h1>
      <p>Segments: {catchAll.join(' / ')}</p>
    </main>
  );
}

3. If you currently destructure synchronously, replace it

This pattern is the one that can trigger the failure:

export default function Page({ params }: { params: { catchAll: string[] } }) {
  return <div>{params.catchAll.join('/')}</div>;
}

Replace it with:

export default async function Page({
  params,
}: {
  params: Promise<{ catchAll: string[] }>;
}) {
  const { catchAll } = await params;

  return <div>{catchAll.join('/')}</div>;
}

If the same route also uses generateMetadata, layout.tsx, or other server-side route handlers that consume the same dynamic segment, make them consistent.

type Props = {
  params: Promise<{ catchAll: string[] }>;
};

export async function generateMetadata({ params }: Props) {
  const { catchAll } = await params;

  return {
    title: `Path: ${catchAll.join('/')}`,
  };
}

export default async function Page({ params }: Props) {
  const { catchAll } = await params;

  return <div>{catchAll.join(' / ')}</div>;
}

5. Rebuild locally before pushing to Vercel

Run your production build locally to confirm the issue is gone.

npm run build

Or:

pnpm build

If the project previously only failed on Vercel, a local production build is the fastest way to validate the fix before redeploying.

6. Verify route behavior for nested paths

Test URLs that actually hit the catch-all segment, such as:

  • /catch-all/a
  • /catch-all/a/b
  • /catch-all/a/b/c

Because catchAll is an array, your rendering logic should expect one or many path segments.

Common Edge Cases

Using [[…catchAll]] instead of […catchAll]

An optional catch-all route can also match the base path with no segments. In that case, catchAll may be undefined, so handle it safely.

export default async function Page({
  params,
}: {
  params: Promise<{ catchAll?: string[] }>;
}) {
  const { catchAll } = await params;
  const segments = catchAll ?? [];

  return <div>{segments.join('/') || 'root'}</div>;
}

Mixing synchronous and asynchronous consumption

If one part of the route tree awaits params and another part still accesses it synchronously, you can end up with inconsistent behavior. Check page.tsx, layout.tsx, and generateMetadata together.

Incorrect TypeScript types

Some projects still declare route props like this:

{ params: { catchAll: string[] } }

That old shape can hide the real issue during development. Update types so the async contract is obvious and enforced.

Assuming catchAll is always a string

In a catch-all route, the segment value is an array of strings, not a single string. Even after fixing the await issue, code like this is still wrong:

const slug = catchAll.toUpperCase();

Instead, select an element or join the array first.

Build passes locally but fails in CI

This can happen when local dependencies, Node.js versions, or Next.js canary behavior differ from Vercel. Make sure the local environment matches your deployment target as closely as possible, especially for Next.js experimental or canary releases.

Reading params inside helper functions without awaiting first

Do not pass unresolved params deep into helpers unless those helpers are also async-aware.

export default async function Page({ params }: { params: Promise<{ catchAll: string[] }> }) {
  const resolved = await params;
  return <div>{formatSegments(resolved.catchAll)}</div>;
}

function formatSegments(segments: string[]) {
  return segments.join(' / ');
}

FAQ

Do I need to await params in every dynamic route?

If your Next.js version and route behavior expose params through the async contract, then yes, you should consume it in the expected async way. This issue specifically shows that catch-all routes can fail on Vercel when you do not.

Why did this work in development but fail during Vercel build?

Development mode and production builds do not always exercise the exact same optimization and validation paths. Vercel production builds can surface stricter checks around dynamic routing, server rendering, and async route data resolution.

Should I use await params even if I only need one field?

Yes. Resolve params first, then read the specific field you need. That keeps your route aligned with the framework’s expected data flow and avoids subtle build-time failures.

The practical takeaway is simple: for a Next.js […catchAll] route in the App Router, treat params as asynchronous, await it before access, and keep your types honest. That small change removes the Vercel build failure and makes the route compatible with the framework’s async rendering model.

Leave a Reply

Your email address will not be published. Required fields are marked *