next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Route "/" did not produce a static shell and Next.js was unable to determine a reason. This is a bug in Next.js.

Open IskanderMustafin opened this issue 2 months ago • 1 comments

Link to the code that reproduces this issue

https://github.com/IskanderMustafin/nextjs-metadata-runtime-bug

To Reproduce

  1. Create a new app: pnpm create next-app@latest next-runtime-data-test --yes
  2. In next.config.ts, set:
     cacheComponents: true,
     htmlLimitedBots: /.*/,

  1. In app/page.tsx add generateMetadata that accesses runtime data (like await searchParams or await connection()) :
   export async function generateMetadata(): Promise<Metadata> {
     await connection()

     return {
       title: 'My App',
       description: 'My App Description',
     }
   }

  1. Run pnpm dev and open / in the browser.
Image Image

Current vs. Expected behavior

In the dev server logs, Next.js repeatedly prints:

Route "/" did not produce a static shell and Next.js was unable to determine a reason. This is a bug in Next.js.

generateMetadata should be allowed to use runtime data like connection() even when cacheComponents: true and htmlLimitedBots: /.*/ are enabled.

Provide environment information

npx --no-install next info

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.1.0: Mon Oct 20 19:34:05 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6041
  Available memory (MB): 24576
  Available CPU cores: 12
Binaries:
  Node: 20.19.4
  npm: 10.8.2
  Yarn: N/A
  pnpm: 10.14.0
Relevant Packages:
  next: 16.0.5 // Latest available version is detected (16.0.5).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

create-next-app, Metadata, cacheComponents

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

next dev (local), next build (local), next start (local)

Additional context

The issue only appears when all three are combined:

  • cacheComponents: true
  • htmlLimitedBots: /.*/ (disabling streaming metadata)
  • generateMetadata calling any runtime data ( e.g. await connection())

Problem happens with any Next.js 16+ (I tried up until 16.0.5)

IskanderMustafin avatar Nov 30 '25 11:11 IskanderMustafin

Fix: Detect dynamicMetadata when prelude is empty in static shell validation

Problem

When generateMetadata uses runtime data (e.g., await connection()) and cacheComponents: true is enabled, Next.js was unable to determine the reason for an empty static shell and threw a generic error:

Route "/" did not produce a static shell and Next.js was unable to determine a reason. This is a bug in Next.js.

This happened because the code checked for dynamicErrors but didn't check for dynamicMetadata when prelude === PreludeState.Empty, even though dynamicMetadata was properly tracked when runtime data was accessed in generateMetadata.

Solution

Added checks for dynamicMetadata in two functions before throwing the generic error:

  1. getStaticShellDisallowedDynamicReasons - Returns the specific dynamicMetadata error instead of the generic error when prelude === PreludeState.Empty
  2. throwIfDisallowedDynamic - Logs and throws the specific dynamicMetadata error instead of the generic error when prelude === PreludeState.Empty

Now, when generateMetadata accesses runtime data and results in an empty static shell, Next.js provides a specific error message:

Route "/route": Runtime data such as `cookies()`, `headers()`, `params`, or `searchParams` was accessed inside `generateMetadata` or you have file-based metadata such as icons that depend on dynamic params segments. Except for this instance, the page would have been entirely prerenderable which may have been the intended behavior. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-metadata

Changes

Code Changes

  • File: packages/next/src/server/app-render/dynamic-rendering.ts
    • Added dynamicMetadata check in getStaticShellDisallowedDynamicReasons (lines 997-1001)
    • Added dynamicMetadata check in throwIfDisallowedDynamic (lines 949-954)

Test Changes

  • New test file: test/e2e/app-dir/cache-components-errors/cache-components-dynamic-metadata-empty-shell.test.ts
  • New fixtures: test/e2e/app-dir/cache-components-errors/fixtures/dynamic-metadata-empty-shell/
    • Test page with generateMetadata using connection()
    • Configuration with cacheComponents: true and htmlLimitedBots: /.*/

How to Test

Manual Testing

  1. Create a new Next.js app:

    pnpm create next-app@latest test-app --yes
    
  2. Configure next.config.ts:

    const nextConfig = {
      cacheComponents: true,
      htmlLimitedBots: /.*/,
    }
    
  3. Create app/page.tsx:

    import { connection } from 'next/server'
    
    export async function generateMetadata() {
      await connection()
      return {
        title: 'My App',
        description: 'My App Description',
      }
    }
    
    export default function Page() {
      return <div>Hello World</div>
    }
    
  4. Run the build:

    pnpm build
    
  5. Expected result: The build should fail with a specific error about dynamicMetadata:

    Route "/": Runtime data such as `cookies()`, `headers()`, `params`, or `searchParams` was accessed inside `generateMetadata`...
    
  6. Before the fix: The build would fail with a generic error:

    Route "/" did not produce a static shell and Next.js was unable to determine a reason. This is a bug in Next.js.
    

Automated Testing

Run the test suite:

# Run the specific test
npm test -- test/e2e/app-dir/cache-components-errors/cache-components-dynamic-metadata-empty-shell.test.ts

# Or run all cache-components-errors tests
npm test -- test/e2e/app-dir/cache-components-errors/

The test verifies:

  • In dev mode: Shows correct error in redbox with dynamic metadata message
  • In build mode: Returns specific error about dynamicMetadata instead of generic error
  • The generic error "Next.js was unable to determine a reason" does NOT appear

Related Issue

Fixes #86664

rosbitskyy avatar Nov 30 '25 17:11 rosbitskyy