Skip to content

Child generateStaticParams skips pre-rendering ALL static params when returning empty array ([]) for SOME parent params #95166

Description

@maxijonson

Link to the code that reproduces this issue

https://github.com/maxijonson/nextjs-prerender-issue

To Reproduce

(Using App Router on next@16.3.0-canary.67)

  1. Two dynamic segments where the parent segment generates its params from a layout:

    // app/[lang]/layout.tsx
    export function generateStaticParams() {
      return [{ lang: "en" }, { lang: "fr" }];
    }
    export default function LangLayout({ children }: { children: React.ReactNode }) {
      return children;
    }
  2. A nested [slug] page whose generateStaticParams returns a non-empty array for one
    parent value and [] for another:

    // app/[lang]/thing/[slug]/page.tsx
    export function generateStaticParams({ params }: { params: { lang: string } }) {
      return params.lang === "en"
        ? [
            { lang: params.lang, slug: "a" },
            { lang: params.lang, slug: "b" },
          ]
        : [];
    }
    export default async function Page({ params }: { params: Promise<{ lang: string; slug: string }> }) {
      const { lang, slug } = await params;
      return (
        <div>
          {lang} {slug}
        </div>
      );
    }
  3. For contrast, a sibling that returns the same non-empty set, but for every parent (the positive control):

    // app/[lang]/thing-control/[slug]/page.tsx
    export function generateStaticParams({ params }: { params: { lang: string } }) {
      return [
        { lang: params.lang, slug: "a" },
        { lang: params.lang, slug: "b" },
      ];
    }
    // same default Page as above
  4. Build:

    rm -rf .next && npx next build

Current vs. Expected behavior

Expected: /en/thing/a and /en/thing/b prerender (the en GSP returned them).
fr simply contributes no static children. This is the documented bottom-up/top-down
nested generateStaticParams behavior.

Actual: thing/[slug] prerenders zero pages — the valid en pages are dropped too.
The route is still printed as (SSG) but emits no HTML and has no entries in the
prerender manifest. The only difference from the control route is the fr return value
([] vs [{slug:"a"},{slug:"b"}]), yet that empty return poisons the entire route.

Build output:

├ ● /[lang]/thing-control/[slug]
│ ├ /en/thing-control/a
│ ├ /en/thing-control/b
│ ├ /fr/thing-control/a
│ └ /fr/thing-control/b
└ ● /[lang]/thing/[slug]        <-- no child paths, 0 pages

Verification:

find .next/server/app -path '*/thing/*' -name '*.html'        # bug route  => empty
find .next/server/app -path '*thing-control*' -name '*.html'  # control    => 4 files

Reproduced across versions and bundlers

Next Turbopack Webpack (next build --webpack)
16.2.4 0 pages (bug) 0 pages (bug)
16.2.9 0 pages (bug) 0 pages (bug)
16.3.0-canary.67 0 pages (bug)

Not Turbopack-specific (webpack reproduces it), not fixed in the latest stable, and still
present on canary.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.5.0 (macOS, arm64 / Apple Silicon)
Binaries:
  Node: 24.x
  npm: 11.x
Relevant Packages:
  next: 16.2.4 (also reproduced on 16.2.9 and 16.3.0-canary.67)
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Dynamic Routes

Which stage(s) are affected? (Select all that apply)

next build (local)

Additional context

Workaround

Returning a placeholder slug for the empty parent case works around the issue, but is not ideal. This is also the workaround documented for Cache Components when all param values are unknown. However, in this case, all params are known, they're just known to not exist under a subset of the parent segments.

export function generateStaticParams({ params }: { params: { lang: string } }) {
  return params.lang === "en"
    ? [{ lang: params.lang, slug: "a" }, { lang: params.lang, slug: "b" }]
    : [{ lang: params.lang, slug: "_" }]; // placeholder slug
}
└   /[lang]/thing/[slug]
  ├ ● /en/thing/a
  ├ ● /en/thing/b
  └ ● /fr/thing/_

Metadata

Metadata

Assignees

Labels

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions