nextjs - 💡(How to fix) Fix force-dynamic incorrectly forces entire route tree to become dynamic (create-component-tree.tsx bug) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86423Fetched 2026-04-08 02:11:06
View on GitHub
Comments
1
Participants
2
Timeline
10
Reactions
0
Author
Timeline (top)
labeled ×6closed ×1commented ×1issue_type_added ×1

The current behavior breaks the documented App Router expectation that dynamic/static settings are scoped per segment. Instead, dynamic mode is applied to the entire tree, causing significant loss of static optimization.

Root Cause

This happens because workStore.forceDynamic is mutated globally during tree traversal inside:

Code Example

npx create-next-app@latest --example reproduction-template force-dynamic-bug
cd force-dynamic-bug

---

app/
├── layout.tsx
├── page.tsx
└── nested/
    ├── layout.tsx
    └── page.tsx

---

export const dynamic = 'force-static'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

---

export const dynamic = 'force-static'

export default function NestedLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div>
      <h1>Nested Layout (should be static)</h1>
      {children}
    </div>
  )
}

---

export const dynamic = 'force-dynamic'

export default function NestedPage() {
  return (
    <div>
      <p>This page should be dynamic: {Date.now()}</p>
    </div>
  )
}

---

npm run build

---

export const dynamic = "force-dynamic"

---

packages/next/src/server/app-render/create-component-tree.tsx

---

// @TODO this does not actually do what it seems like it would or should do.
// ... the entire render becomes force-dynamic.
// We should refactor this function so that we can correctly track which segments
// need to be dynamic.

---

export const dynamic = "force-dynamic"

---

export const dynamic = "force-static"

---

Please find the output of `next info` below:

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0

Binaries:
  Node: 20.17.0
  npm: 10.8.2
  Yarn: 1.22.22
  pnpm: 9.10.0

Relevant Packages:
  next: 15.0.0-canary.148
  eslint-config-next: 15.0.0-canary.148
  react: 19.0.0-rc-3dfd5d9e-20240910
  react-dom: 19.0.0-rc-3dfd5d9e-20240910
  typescript: 5.6.2

Next.js Config:
  output: default

---

15.0.0-canary.148

---

15.0.0-canary.147
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/user/my-minimal-nextjs-issue-reproduction

To Reproduce

To Reproduce

  1. Create a new Next.js app using the reproduction template:
npx create-next-app@latest --example reproduction-template force-dynamic-bug
cd force-dynamic-bug
  1. Create the following file structure inside app/:
app/
├── layout.tsx
├── page.tsx
└── nested/
    ├── layout.tsx
    └── page.tsx
  1. Add export const dynamic = 'force-static' to both layout files.

app/layout.tsx

export const dynamic = 'force-static'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

app/nested/layout.tsx

export const dynamic = 'force-static'

export default function NestedLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div>
      <h1>Nested Layout (should be static)</h1>
      {children}
    </div>
  )
}
  1. Add export const dynamic = 'force-dynamic' to the nested page.

app/nested/page.tsx

export const dynamic = 'force-dynamic'

export default function NestedPage() {
  return (
    <div>
      <p>This page should be dynamic: {Date.now()}</p>
    </div>
  )
}
  1. Run the production build:
npm run build
  1. Observe the behavior:
  • Parent layouts with dynamic = "force-static" are incorrectly forced to become dynamic.
  • Entire route branch becomes dynamic due to the nested page.
  • Static optimization for parent layouts is lost.

Current vs. Expected behavior

Current vs. Expected behavior

❌ Current Behavior (Bug)

When the nested page exports:

export const dynamic = "force-dynamic"

Next.js incorrectly applies this dynamic setting to:

  • the parent layout (app/layout.tsx),
  • the nested layout (app/nested/layout.tsx),
  • and the entire route branch above it.

This happens because workStore.forceDynamic is mutated globally during tree traversal inside:

packages/next/src/server/app-render/create-component-tree.tsx

As a result:

  • layouts explicitly marked dynamic = "force-static" are ignored
  • static generation is disabled for the entire branch
  • partial prerendering (PPR) is broken
  • unnecessary runtime rendering occurs
  • dynamic boundaries propagate upward when they should not

Next.js’ own comment in the code acknowledges this is incorrect behavior:

// @TODO this does not actually do what it seems like it would or should do.
// ... the entire render becomes force-dynamic.
// We should refactor this function so that we can correctly track which segments
// need to be dynamic.

✅ Expected Behavior

Only the segment that explicitly declares:

export const dynamic = "force-dynamic"

should be forced dynamic.

The parent layouts should remain static, because they explicitly declare:

export const dynamic = "force-static"

Therefore:

  • /nested/page → dynamic (correct)
  • /nested/layout → static (expected)
  • /layout → static (expected)

Dynamic flags should not propagate upward to ancestors or sibling segments.

Summary

The current behavior breaks the documented App Router expectation that dynamic/static settings are scoped per segment. Instead, dynamic mode is applied to the entire tree, causing significant loss of static optimization.

Provide environment information

Please find the output of `next info` below:

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0

Binaries:
  Node: 20.17.0
  npm: 10.8.2
  Yarn: 1.22.22
  pnpm: 9.10.0

Relevant Packages:
  next: 15.0.0-canary.148
  eslint-config-next: 15.0.0-canary.148
  react: 19.0.0-rc-3dfd5d9e-20240910
  react-dom: 19.0.0-rc-3dfd5d9e-20240910
  typescript: 5.6.2

Next.js Config:
  output: default

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

Parallel & Intercepting Routes, Partial Prerendering (PPR), Dynamic Routes, Runtime, Loading UI and Streaming, Performance

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

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

Additional context

Additional context

  • The issue is reproducible locally and on Vercel deployments.
  • It occurs consistently across multiple canary releases.
  • The first canary where I detected the bug was:
15.0.0-canary.148

Rolling back to:

15.0.0-canary.147

still reproduces the issue since the bug originates from logic in
create-component-tree.tsx, where workStore.forceDynamic is applied globally.

  • The behavior occurs regardless of hosting method (local, Vercel, Docker).
  • The issue is not browser-specific.
  • The bug stems from dynamic mode incorrectly propagating upward during tree traversal.

extent analysis

TL;DR

The most likely fix for this issue is to refactor the create-component-tree.tsx function to correctly track which segments need to be dynamic, rather than applying workStore.forceDynamic globally.

Guidance

  • Identify the specific lines of code in create-component-tree.tsx where workStore.forceDynamic is being mutated globally and refactor to track dynamic segments correctly.
  • Verify that the dynamic flags are not propagating upward to ancestors or sibling segments by checking the dynamic property of each segment.
  • Test the fix by running the production build and observing the behavior of the parent layouts and nested page.
  • Consider creating a test case to ensure that the dynamic flags are being applied correctly in different scenarios.

Example

// create-component-tree.tsx
// ...
// Instead of mutating workStore.forceDynamic globally
// workStore.forceDynamic = true;

// Track dynamic segments correctly
const dynamicSegments = [];
// ...
if (segment.dynamic === 'force-dynamic') {
  dynamicSegments.push(segment);
}
// ...

Notes

  • The issue is specific to Next.js version 15.0.0-canary.148 and later.
  • The fix may require changes to the create-component-tree.tsx function and potentially other related code.
  • It's essential to thoroughly test the fix to ensure that it doesn't introduce any new issues.

Recommendation

Apply a workaround by downgrading to Next.js version 15.0.0-canary.147 or earlier, where the bug is not present, until a proper fix is available.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - 💡(How to fix) Fix force-dynamic incorrectly forces entire route tree to become dynamic (create-component-tree.tsx bug) [1 comments, 2 participants]