Skip to content

Turbopack: fix split_module correctness for app-router - #95522

Open
jimmyhmiller wants to merge 4 commits into
jimmym/fix-advanced-tree-shake-emptyfrom
jimmym/tree-shaking-skip-internal
Open

Turbopack: fix split_module correctness for app-router#95522
jimmyhmiller wants to merge 4 commits into
jimmym/fix-advanced-tree-shake-emptyfrom
jimmym/tree-shaking-skip-internal

Conversation

@jimmyhmiller

@jimmyhmiller jimmyhmiller commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Turbopack: fix split_module correctness for app-router

  • Don't split the generated __nextjs-internal-proxy. client-reference
    proxy module. Its exports are all used, so splitting yields no benefit
    and duplicates its part in the module graph, tripping the
    duplicate-ident check in ModuleGraph::from_graphs.
  • Pass the original comments to each fragment so magic-comment
    annotations (turbopackChunkingType, turbopackExports) are still
    parsed. Fragment ASTs keep their original spans, so comments resolve
    at the same byte positions. Without this, references fall back to
    defaults (ChunkingType::Shared -> Parallel), breaking app-router
    layout-segment chunk groups.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 814ms 813ms ███▁█
Cold (Ready in log) 784ms 798ms ▅▄▄▁▄
Cold (First Request) 1.305s 1.342s ▄▂▃▁▂
Warm (Listen) 812ms 813ms ███▁█
Warm (Ready in log) 788ms 796ms ▄▄▅▁▄
Warm (First Request) 1.298s 1.315s ▂▄▄▂▃
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 813ms 814ms █████
Cold (Ready in log) 786ms 794ms ▅█▃▄▃
Cold (First Request) 3.473s 3.428s ▂▃▁▂▁
Warm (Listen) 813ms 813ms ████▁
Warm (Ready in log) 789ms 798ms ▅█▃▃▂
Warm (First Request) 3.488s 3.561s ▂▄▁▂▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 5.269s 5.264s ▅▃▄▁▄
Cached Build 5.345s 5.514s ▇▃▃▁▂
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 24.246s 24.466s ██▄▆▃
Cached Build 24.822s 24.680s ██▂▃▁
node_modules Size 549 MB 549 MB █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
00984xh3ankve.js gzip 7.4 kB N/A -
0a-p0meqxynjz.js gzip 10.3 kB N/A -
0avtke4yefj4-.js gzip 13.1 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0gnv6eyj01c8a.js gzip 161 B N/A -
0pt5of3ch6d2u.js gzip 13.6 kB N/A -
0v9a5amb-uhhp.js gzip 156 B N/A -
0xnsdo-6y9r3k.js gzip 5.72 kB N/A -
0yi7is8u4covm.js gzip 8.77 kB N/A -
15j9yp4dvsbr1.js gzip 156 B N/A -
177ancdmsmuoo.js gzip 153 B N/A -
1a_6zw_e21-qq.js gzip 158 B N/A -
1b-w668w_zbqu.js gzip 65.6 kB N/A -
1chazj9ksmvti.js gzip 162 B N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1g9umknojmhva.js gzip 169 B N/A -
1prv-wg6jgtt2.js gzip 8.77 kB N/A -
1t47vod02e_4a.js gzip 8.75 kB N/A -
1tf1phijqlx9j.js gzip 220 B 220 B
1tw002sq5bo4l.js gzip 10 kB N/A -
1wlerik1n7zbr.js gzip 8.7 kB N/A -
1xt37r1-3770e.js gzip 155 B N/A -
2lviwqiqxm904.js gzip 3.52 kB N/A -
2n_3zfrnh-doc.js gzip 155 B N/A -
2pevdqjpbf3xk.js gzip 9.45 kB N/A -
2qd2anu9eizlz.js gzip 8.75 kB N/A -
2sv39ae5_8vfj.js gzip 8.81 kB N/A -
2th13-6o_x62w.js gzip 155 B N/A -
2utx6n7w65xqa.js gzip 8.7 kB N/A -
3-ukivglqkfeq.js gzip 8.78 kB N/A -
30s3bi0lj4tac.js gzip 155 B N/A -
325xisqrylugu.js gzip 450 B N/A -
379dii5ntojsp.js gzip 45.4 kB N/A -
384u4tlbbqdk9.js gzip 155 B N/A -
3jc9ghvds1spf.js gzip 2.29 kB N/A -
3jh99rm2yrkbs.js gzip 71.2 kB N/A -
3juwkvkgvywfe.js gzip 10.6 kB N/A -
3kbove7q58uqh.js gzip 13.2 kB N/A -
3o2-gqs20ypbv.js gzip 157 B N/A -
3qn7vntp30gaj.js gzip 1.47 kB N/A -
turbopack-04..kawt.js gzip 3.81 kB N/A -
turbopack-0g..tgnn.js gzip 3.81 kB N/A -
turbopack-0p..brju.js gzip 3.8 kB N/A -
turbopack-0s..s8uc.js gzip 3.82 kB N/A -
turbopack-14..4-ox.js gzip 3.81 kB N/A -
turbopack-20..fcqd.js gzip 3.81 kB N/A -
turbopack-25..hh_l.js gzip 3.81 kB N/A -
turbopack-26..fz5l.js gzip 3.81 kB N/A -
turbopack-2l..wsrs.js gzip 3.8 kB N/A -
turbopack-2r..l2_y.js gzip 3.81 kB N/A -
turbopack-37..9mqr.js gzip 3.81 kB N/A -
turbopack-3i..yxx_.js gzip 3.81 kB N/A -
turbopack-3x..6658.js gzip 3.79 kB N/A -
turbopack-40..o3-s.js gzip 3.81 kB N/A -
030a1njeh5b77.js gzip N/A 10 kB -
040byd99yski1.js gzip N/A 157 B -
06h7qjryxd6q_.js gzip N/A 71.2 kB -
0f5gxnfqdomje.js gzip N/A 156 B -
0fsconcqw__ge.js gzip N/A 155 B -
0ot0qt5np20h7.js gzip N/A 8.78 kB -
12m9rr7_7cexd.js gzip N/A 8.7 kB -
1504ulb4wy-ci.js gzip N/A 168 B -
186r3y_rfvr0b.js gzip N/A 8.78 kB -
19v97848yrjp7.js gzip N/A 8.81 kB -
1d6k_0r45ituk.js gzip N/A 158 B -
1infbfbijhrf3.js gzip N/A 155 B -
1nd_0rwgz2ozk.js gzip N/A 9.45 kB -
1oi9pltr_e10m.js gzip N/A 10.6 kB -
1px_yvc50vdqw.js gzip N/A 155 B -
1rd9tzqgzsz2w.js gzip N/A 8.77 kB -
1rq1g_cwv642d.js gzip N/A 10.3 kB -
1uojw_uabeg93.js gzip N/A 450 B -
1w-7lusgyl81y.js gzip N/A 8.7 kB -
21h56wdn9zddo.js gzip N/A 8.75 kB -
2ezbkax1wpax7.js gzip N/A 156 B -
2hifxandmlxt6.js gzip N/A 156 B -
2lg10omv7xu14.js gzip N/A 3.52 kB -
2ovff1533zvno.js gzip N/A 13.2 kB -
2p40ojwrzdckr.js gzip N/A 45.4 kB -
2s4z8jy0z7v93.js gzip N/A 1.46 kB -
2w9le8joqnx61.js gzip N/A 7.41 kB -
2y6gb5bs4mnam.js gzip N/A 8.75 kB -
340uo343e-ols.js gzip N/A 152 B -
341h71ln1jpl3.js gzip N/A 2.29 kB -
34ttpt_n7605h.js gzip N/A 5.72 kB -
36ckfq9o-ys-a.js gzip N/A 13.1 kB -
3a8nxso6ehjpa.js gzip N/A 65.6 kB -
3bkm4u2nqqu8u.js gzip N/A 161 B -
3euiwa3al4zcz.js gzip N/A 155 B -
3nz1-rn_lqqgn.js gzip N/A 161 B -
3vdh6_u50pu5t.js gzip N/A 13.6 kB -
turbopack-03..vf45.js gzip N/A 3.8 kB -
turbopack-0o..ej5r.js gzip N/A 3.82 kB -
turbopack-0p..s82b.js gzip N/A 3.8 kB -
turbopack-0q..k6q-.js gzip N/A 3.8 kB -
turbopack-1a..mxjn.js gzip N/A 3.8 kB -
turbopack-1t..im68.js gzip N/A 3.8 kB -
turbopack-21..2zyd.js gzip N/A 3.8 kB -
turbopack-23..bnkp.js gzip N/A 3.78 kB -
turbopack-27..wy4e.js gzip N/A 3.81 kB -
turbopack-27..cr0_.js gzip N/A 3.8 kB -
turbopack-29..j3-e.js gzip N/A 3.8 kB -
turbopack-2o..revh.js gzip N/A 3.8 kB -
turbopack-2w..9ns4.js gzip N/A 3.8 kB -
turbopack-30..-la7.js gzip N/A 3.8 kB -
Total 448 kB 448 kB ⚠️ +10 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 774 B 775 B
Total 774 B 775 B ⚠️ +1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 429 B 433 B
Total 429 B 433 B ⚠️ +4 B

📦 Webpack

Client

Main Bundles
Canary PR Change
3322-HASH.js gzip 64 kB N/A -
4191.HASH.js gzip 169 B N/A -
7920-HASH.js gzip 4.68 kB N/A -
9784-HASH.js gzip 5.63 kB N/A -
b1ad9f4c-HASH.js gzip 62.9 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 254 B 253 B
main-HASH.js gzip 40 kB 40 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
3577.HASH.js gzip N/A 168 B -
578-HASH.js gzip N/A 64.6 kB -
8590-HASH.js gzip N/A 5.61 kB -
9750-HASH.js gzip N/A 4.69 kB -
a8984546-HASH.js gzip N/A 62.9 kB -
Total 239 kB 240 kB ⚠️ +598 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 193 B
_error-HASH.js gzip 181 B 182 B
css-HASH.js gzip 334 B 331 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 255 B 253 B
head-HASH.js gzip 349 B 351 B
hooks-HASH.js gzip 382 B 384 B
image-HASH.js gzip 581 B 582 B
index-HASH.js gzip 260 B 259 B
link-HASH.js gzip 2.48 kB 2.48 kB
routerDirect..HASH.js gzip 317 B 318 B
script-HASH.js gzip 384 B 386 B
withRouter-HASH.js gzip 316 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.95 kB 7.95 kB ⚠️ +1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 129 kB 128 kB
page.js gzip 291 kB 292 kB
Total 420 kB 420 kB ⚠️ +423 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 619 B 617 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 45.6 kB 45.2 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 47.2 kB 46.8 kB ✅ -427 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 718 B 718 B
Total 718 B 718 B
Build Cache
Canary PR Change
0.pack gzip 4.69 MB 4.69 MB 🟢 5.74 kB (0%)
index.pack gzip 123 kB 122 kB
index.pack.old gzip 123 kB 123 kB
Total 4.94 MB 4.93 MB ✅ -6.96 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 370 kB 370 kB
app-page-exp..prod.js gzip 204 kB 204 kB
app-page-tur...dev.js gzip 370 kB 370 kB
app-page-tur..prod.js gzip 204 kB 204 kB
app-page-tur...dev.js gzip 366 kB 366 kB
app-page-tur..prod.js gzip 202 kB 202 kB
app-page.run...dev.js gzip 367 kB 367 kB
app-page.run..prod.js gzip 202 kB 202 kB
app-route-ex...dev.js gzip 82.5 kB 82.5 kB
app-route-ex..prod.js gzip 55.9 kB 55.9 kB
app-route-tu...dev.js gzip 82.5 kB 82.5 kB
app-route-tu..prod.js gzip 55.9 kB 55.9 kB
app-route-tu...dev.js gzip 82.1 kB 82.1 kB
app-route-tu..prod.js gzip 55.7 kB 55.7 kB
app-route.ru...dev.js gzip 82.1 kB 82.1 kB
app-route.ru..prod.js gzip 55.7 kB 55.7 kB
dev-validati...dev.js gzip 132 kB 132 kB
dev-validati...dev.js gzip 132 kB 132 kB
dev-validati...dev.js gzip 130 kB 130 kB
dev-validati...dev.js gzip 130 kB 130 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 45.4 kB 45.4 kB
pages-api-tu..prod.js gzip 34.1 kB 34.1 kB
pages-api.ru...dev.js gzip 45.4 kB 45.4 kB
pages-api.ru..prod.js gzip 34.1 kB 34.1 kB
pages-turbo....dev.js gzip 54.9 kB 54.9 kB
pages-turbo...prod.js gzip 39.8 kB 39.8 kB
pages.runtim...dev.js gzip 54.9 kB 54.9 kB
pages.runtim..prod.js gzip 39.7 kB 39.7 kB
server.runti..prod.js gzip 68.2 kB 68.2 kB
use-cache-pr...dev.js gzip 72 kB 72 kB
use-cache-pr...dev.js gzip 72 kB 72 kB
use-cache-pr...dev.js gzip 70.3 kB 70.3 kB
use-cache-pr...dev.js gzip 70.3 kB 70.3 kB
Total 4.07 MB 4.07 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/ab1a79f318ff583ba3f89fbcad9a5cef25c573eb/next

Commit: ab1a79f

@jimmyhmiller jimmyhmiller changed the title jimmym/tree shaking skip internal Turbopack: fix split_module correctness for app-router Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Failing test suites

Commit: ab1a79f | About building and testing Next.js

pnpm test-dev test/development/app-dir/cache-components-dev-warmup/cache-components.dev-warmup.with-prefetch-config.initial-load.test.ts (job)

  • cache-components-dev-warmup - with runtime prefetch configs > initial load > cached data resolves in the correct phase > cached data + short-stale cached data (DD)
Expand output

● cache-components-dev-warmup - with runtime prefetch configs › initial load › cached data resolves in the correct phase › cached data + short-stale cached data

expect(received).toEqual(expected) // deep equality

- Expected  - 1
+ Received  + 1

  Array [
-   "Prerender",
+   "Server",
    StringContaining "after cache read - layout",
  ]

  72 |       const actualMessageText = messages[0]
  73 |       const [, actualEnvironment] = actualMessageText.match(logPattern)!
> 74 |       expect([actualEnvironment, actualMessageText]).toEqual([
     |                                                      ^
  75 |         expectedEnvironment,
  76 |         expect.stringContaining(message),
  77 |       ])

  at toEqual (development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts:74:54)
  at assertLog (development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts:319:13)
  at retry (lib/next-test-utils.ts:868:14)
  at testInitialLoad (development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts:99:7)
  at Object.<anonymous> (development/app-dir/cache-components-dev-warmup/dev-warmup.util.ts:330:13)

@jimmyhmiller
jimmyhmiller force-pushed the jimmym/tree-shaking-skip-internal branch from d30cfc8 to f57969c Compare July 6, 2026 19:00
@jimmyhmiller

Copy link
Copy Markdown
Contributor Author

Not the most elegant approach. But I found that this internal module was causing these problems. I saw that there were a number of other places we added an exception. So I followed suit.

@jimmyhmiller
jimmyhmiller marked this pull request as ready for review July 7, 2026 14:20
@jimmyhmiller
jimmyhmiller requested a review from a team July 7, 2026 15:24
Comment on lines 491 to 492
.cell());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Re: lines +486 to +492]

i wonder if this bug is still real?

See this comment inline on Graphite.

Comment thread turbopack/crates/turbopack-ecmascript/src/tree_shake/mod.rs Outdated
@jimmyhmiller
jimmyhmiller force-pushed the jimmym/tree-shaking-skip-internal branch 2 times, most recently from a824138 to 0440399 Compare July 9, 2026 13:54
Comment on lines +167 to +171
// Tag the ident with a `ModulePart::Exports` part so tree-shaking's
// `split_module` opt-out (`!ident.parts.is_empty()`) skips this module.
// Because of the state of this feature, it is unclear if this is competely
// the right course of action. Not skipping this causes "Duplicate module idents"
// errors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would actually be nice to split this up (though the savings are marginaly(

Why do you get duplicate module idents? Shouldn't the module splitting add (internal part X) to all modules?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a more fundamental bug in split_module.
You might have your own custom Module via a module rule that doesn't have a ModulePart in the ident either. Then you'd also run into this bug.

- Don't split the generated __nextjs-internal-proxy. client-reference
  proxy module. Its exports are all used, so splitting yields no benefit
  and duplicates its <exports> part in the module graph, tripping the
  duplicate-ident check in ModuleGraph::from_graphs.
- Pass the original comments to each fragment so magic-comment
  annotations (turbopackChunkingType, turbopackExports) are still
  parsed. Fragment ASTs keep their original spans, so comments resolve
  at the same byte positions. Without this, references fall back to
  defaults (ChunkingType::Shared -> Parallel), breaking app-router
  layout-segment chunk groups.

Add e2e coverage for app-router and pages.
Tag the generated __nextjs-internal-proxy module's ident with
ModulePart::Exports so it hits split_module's existing
!ident.parts.is_empty() opt-out, instead of matching the module name
inside split_module. This keeps the decision at the proxy's construction
site in next-core rather than hardcoding a Next.js-specific name in the
general-purpose turbopack-ecmascript crate. Behavior is unchanged: the
proxy is still not split, avoiding the duplicate <exports> part that
tripped the duplicate-ident check in ModuleGraph::from_graphs.
The client-reference proxy ident now carries a ModulePart::Exports suffix (e.g. ` <exports>`), so its source-map source no longer ends with the .mjs/.cjs extension. Match the base name with contains() instead of ends_with() so the proxy frame stays ignore-listed and does not leak into runtime error stacks (fixes use-cache-errors redbox snapshot).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants