Dynamic route works in dev but fails in production with next build

18 replies
Last updated: Jan 17, 2026
Can anyone here think of a reason that a dynamic route might work in dev mode but not in production mode when running
next build && next start
? We have a project we are working on that has a dynamic
path
being created in the query to sanity, and we are using a wildcard
[path].tsx
to catch those routes. The query seems to work fine, and in dev we can get to all the pages using this route. But in production we get empty pages for
/page1
, and 404s for
/page1/childpage
AI Update

This is a classic Next.js static generation issue! The problem is almost certainly that your dynamic routes aren't being pre-rendered at build time. Here's what's happening:

The Root Cause:

In development mode (next dev), Next.js generates pages on-demand for every request. But in production (next build && next start), Next.js uses static generation by default, which means pages need to be either:

  1. Pre-rendered at build time, OR
  2. Configured to generate on-demand with proper fallback handling

Since you're using a catch-all route like [path].tsx, you need to implement getStaticPaths() (Pages Router) or generateStaticParams() (App Router) to tell Next.js which paths to pre-render.

The Solution:

For Pages Router ([path].tsx):

export async function getStaticPaths() {
  const paths = await client.fetch(
    `*[_type == "yourType" && defined(path)][].path`
  );

  return {
    paths: paths.map((path) => ({ params: { path } })),
    fallback: 'blocking', // or true, see below
  };
}

export async function getStaticProps({ params }) {
  const { path } = params;
  const data = await client.fetch(
    `*[_type == "yourType" && path == $path][0]`,
    { path }
  );
  
  return {
    props: { data },
    revalidate: 60, // Optional: ISR
  };
}

For App Router (app/[path]/page.tsx):

export async function generateStaticParams() {
  const paths = await client.fetch(
    `*[_type == "yourType" && defined(path)][].path`
  );

  return paths.map((path) => ({ path }));
}

Understanding Fallback Options:

The fallback property in getStaticPaths is crucial:

  • fallback: false - Returns 404 for any path not generated at build time
  • fallback: true - Generates pages on-demand, but requires loading state handling
  • fallback: 'blocking' - Generates pages on-demand and waits before serving (no loading state needed)

Why You're Seeing Different Behaviors:

  • /page1 showing empty: The page is rendering but getStaticProps isn't fetching data because the path wasn't pre-rendered
  • /page1/childpage showing 404: Next.js doesn't know this route exists at all

For Catch-All Routes Specifically:

If you're using [...path].tsx to capture nested routes like /page1/childpage, make sure your params structure matches:

// For [...path].tsx (catch-all)
paths: allPaths.map((pathString) => ({
  params: { path: pathString.split('/') } // Array for catch-all
}))

// For [path].tsx (single segment)
paths: allPaths.map((pathString) => ({
  params: { path: pathString } // String for single segment
}))

The key takeaway: production builds require explicit path generation. Without getStaticPaths or generateStaticParams, Next.js doesn't know which dynamic routes to create at build time!

Show original thread
18 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?