Next.js - Dynamic Route not working in Production Mode when running `Next Build && Next Start`

18 replies
Last updated: Feb 12, 2021
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!

This here
[path].tsx
catches only the first level of path like
/page1
and
/page2
. Not the sub path routes!! To catch all of them you have to use this one: https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes
[…path].tsx
Yeah, I tried that and it seems to fix the 404s. What it doesn't help with is why the pages are rendering empty when in production mode, but working fine in dev mode
In the dev mode 404 pages a not supported
no, sorry... not what i mean.
So, I've changed to the catch-all route
That route, in dev mode, returns a page with content from Sanity as expected
in production mode it returns empty
i think i might have run into this and it was because i had a collision with a static and dynamic page. i had a hard coded page (about.tsx) and then a dynamic route ([slug].tsx) where sanity also has an about slug. not sure if that's related
The hard coded page should always wins as i know 🙂
when i had the collision, i remember the dang kind of bug where it didn't show up when deployed but was fine locally. best to just remove all collisions
learned that the hard way lol
Hmm. There shouldn't be a collision between paths I don't think. Thanks for the input though... this is an odd one
Are you using getStaticProps and getStaticPaths,
user M
? Also, are you deploying on Vercel? If so, your Function Logs might provide some insight.
Deploying on Netlify at the moment, but have the same issue locally. Have managed to use
getStaticProps
and
getStaticPaths
to get things to render properly on first load, but now we have a different issue - when clicking a link to change to another page that uses the same route but has different components, the page is not re-rendering properly, it looks like its only updating the page title
When you’re navigating between pages locally, what shows up in your terminal where you’re running the dev server? Is it similar to this?
Sorry I kind of ghosted there... Got sucked into actually fixing the thing and then had meetings etc. It turns out that the way our guys were serializing the content wasn't refreshing the props when the page changed. So they sorted that and now the rendering updates as it should! Thanks for all the help!
That’s great! I’m happy to hear it. Thanks for the update. 🙂

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?