Route "/" did not produce a static shell and Next.js was unable to determine a reason. This is a bug in Next.js.
Link to the code that reproduces this issue
https://github.com/IskanderMustafin/nextjs-metadata-runtime-bug
To Reproduce
- Create a new app:
pnpm create next-app@latest next-runtime-data-test --yes - In
next.config.ts, set:
cacheComponents: true,
htmlLimitedBots: /.*/,
- In
app/page.tsxadd generateMetadata that accesses runtime data (likeawait searchParamsorawait connection()) :
export async function generateMetadata(): Promise<Metadata> {
await connection()
return {
title: 'My App',
description: 'My App Description',
}
}
- Run
pnpm devand open/in the browser.
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) -
generateMetadatacalling any runtime data ( e.g.await connection())
Problem happens with any Next.js 16+ (I tried up until 16.0.5)
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:
-
getStaticShellDisallowedDynamicReasons- Returns the specificdynamicMetadataerror instead of the generic error whenprelude === PreludeState.Empty -
throwIfDisallowedDynamic- Logs and throws the specificdynamicMetadataerror instead of the generic error whenprelude === 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
dynamicMetadatacheck ingetStaticShellDisallowedDynamicReasons(lines 997-1001) - Added
dynamicMetadatacheck inthrowIfDisallowedDynamic(lines 949-954)
- Added
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
generateMetadatausingconnection() - Configuration with
cacheComponents: trueandhtmlLimitedBots: /.*/
- Test page with
How to Test
Manual Testing
-
Create a new Next.js app:
pnpm create next-app@latest test-app --yes -
Configure
next.config.ts:const nextConfig = { cacheComponents: true, htmlLimitedBots: /.*/, } -
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> } -
Run the build:
pnpm build -
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`... -
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
dynamicMetadatainstead of generic error - The generic error "Next.js was unable to determine a reason" does NOT appear
Related Issue
Fixes #86664