Skip to content

Skip empty parallel route slot directories instead of erroring#92708

Draft
feedthejim wants to merge 1 commit intocanaryfrom
fix/empty-parallel-route-slot
Draft

Skip empty parallel route slot directories instead of erroring#92708
feedthejim wants to merge 1 commit intocanaryfrom
fix/empty-parallel-route-slot

Conversation

@feedthejim
Copy link
Copy Markdown
Contributor

Summary

  • Empty @-prefixed directories with no route files (e.g. leftover empty directories from branch switches or stale git state) caused a build error: Missing required default.js file for parallel route
  • Both webpack and Turbopack now check that @-slot directories actually contain route files before treating them as parallel route slots
  • Empty slots are silently ignored instead of requiring a default.js

Webpack (next-app-loader)

  • Added directoryHasFiles() helper that recursively checks for non-dotfiles
  • resolveAdjacentParallelSegments now skips empty @ directories

Turbopack (app_structure.rs)

  • Added AppDirModules::is_empty() and PlainDirectoryTree::has_any_route_files()
  • The fallback condition for parallel routes now requires actual route content

Test plan

  • Added test fixture with empty @empty-slot directory containing empty catch-all subdirectories
  • Test verifies build succeeds and pages render with empty slot present
  • Test verifies child routes still work with empty slot present
  • Existing parallel route tests continue to pass

🤖 Generated with Claude Code

@nextjs-bot nextjs-bot added created-by: Next.js team PRs by the Next.js team. Documentation Related to Next.js' official documentation. tests Turbopack Related to Turbopack with Next.js. type: next labels Apr 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

All broken links are now fixed, thank you!

Empty @-prefixed directories with no route files (e.g. leftover empty
directories from branch switches) caused a build error requiring a
default.js. Both webpack and Turbopack now check that @-slot directories
contain actual route files before treating them as parallel route slots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@feedthejim feedthejim force-pushed the fix/empty-parallel-route-slot branch from fc7e720 to 2767417 Compare April 13, 2026 03:20
@nextjs-bot
Copy link
Copy Markdown
Collaborator

nextjs-bot commented Apr 13, 2026

Failing test suites

Commit: 2767417 | About building and testing Next.js

pnpm test-dev test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts (job)

  • parallel-routes-leaf-segments-no-build-error > empty parallel route slot > should build and render successfully when a parallel route directory is empty (DD)
  • parallel-routes-leaf-segments-no-build-error > empty parallel route slot > should render child routes despite the empty parallel route directory (DD)
  • parallel-routes-leaf-segments-no-build-error > leaf segment with catch-all parameter > should render catch-all segment with multiple path segments (DD)
  • parallel-routes-leaf-segments-no-build-error > leaf segment with route groups > should render leaf segment with route groups and parallel slots (DD)
  • parallel-routes-leaf-segments-no-build-error > leaf segment without child routes > should render the leaf segment page with all parallel slots (DD)
  • parallel-routes-leaf-segments-no-build-error > no children slot > should render the no children slot (DD)
Expand output

● parallel-routes-leaf-segments-no-build-error › leaf segment without child routes › should render the leaf segment page with all parallel slots

FetchError: request to http://localhost:34103/leaf-segment failed, reason: socket hang up

  at ClientRequest.<anonymous> (../node_modules/.pnpm/node-fetch@2.6.7_encoding@0.1.13/node_modules/node-fetch/lib/index.js:1491:11)

● parallel-routes-leaf-segments-no-build-error › leaf segment with route groups › should render leaf segment with route groups and parallel slots

FetchError: request to http://localhost:34103/leaf-with-groups failed, reason: connect ECONNREFUSED 127.0.0.1:34103

  at ClientRequest.<anonymous> (../node_modules/.pnpm/node-fetch@2.6.7_encoding@0.1.13/node_modules/node-fetch/lib/index.js:1491:11)

● parallel-routes-leaf-segments-no-build-error › leaf segment with catch-all parameter › should render catch-all segment with multiple path segments

FetchError: request to http://localhost:34103/catch-all-with-parallel/a/b/c failed, reason: connect ECONNREFUSED 127.0.0.1:34103

  at ClientRequest.<anonymous> (../node_modules/.pnpm/node-fetch@2.6.7_encoding@0.1.13/node_modules/node-fetch/lib/index.js:1491:11)

● parallel-routes-leaf-segments-no-build-error › no children slot › should render the no children slot

FetchError: request to http://localhost:34103/no-children/other failed, reason: connect ECONNREFUSED 127.0.0.1:34103

  at ClientRequest.<anonymous> (../node_modules/.pnpm/node-fetch@2.6.7_encoding@0.1.13/node_modules/node-fetch/lib/index.js:1491:11)

● parallel-routes-leaf-segments-no-build-error › empty parallel route slot › should build and render successfully when a parallel route directory is empty

FetchError: request to http://localhost:34103/with-empty-slot failed, reason: connect ECONNREFUSED 127.0.0.1:34103

  at ClientRequest.<anonymous> (../node_modules/.pnpm/node-fetch@2.6.7_encoding@0.1.13/node_modules/node-fetch/lib/index.js:1491:11)

● parallel-routes-leaf-segments-no-build-error › empty parallel route slot › should render child routes despite the empty parallel route directory

FetchError: request to http://localhost:34103/with-empty-slot/child failed, reason: connect ECONNREFUSED 127.0.0.1:34103

  at ClientRequest.<anonymous> (../node_modules/.pnpm/node-fetch@2.6.7_encoding@0.1.13/node_modules/node-fetch/lib/index.js:1491:11)

pnpm test-dev-turbo test/e2e/app-dir/instant-navigation-testing-api/instant-navigation-testing-api.test.ts (turbopack) (job)

  • instant-navigation-testing-api > runtime params are excluded from instant shell > does not include cookie values in instant shell during page load (DD)
Expand output

● instant-navigation-testing-api › runtime params are excluded from instant shell › does not include cookie values in instant shell during page load

expect(received).toContain(expected) // indexOf

Expected substring: "testCookie: hello"
Received string:    "testCookie: not set"

  411 |       const cookieValue = page.locator('[data-testid="cookie-value"]')
  412 |       await cookieValue.waitFor({ state: 'visible', timeout: 10000 })
> 413 |       expect(await cookieValue.textContent()).toContain('testCookie: hello')
      |                                               ^
  414 |     })
  415 |
  416 |     it('does not include dynamic param values in instant shell during page load', async () => {

  at Object.toContain (e2e/app-dir/instant-navigation-testing-api/instant-navigation-testing-api.test.ts:413:47)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 13, 2026

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing fix/empty-parallel-route-slot (2767417) with canary (fda3505)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@nextjs-bot
Copy link
Copy Markdown
Collaborator

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) 455ms 455ms ████▁
Cold (Ready in log) 443ms 442ms ▅▆▅██
Cold (First Request) 834ms 832ms ▁▇▇▁▁
Warm (Listen) 456ms 455ms ███▁▁
Warm (Ready in log) 442ms 442ms ▁▇█▃▄
Warm (First Request) 342ms 342ms ▁▅▅▂▃
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 455ms 455ms ██▁▁▁
Cold (Ready in log) 437ms 436ms ▁▂▂▁▅
Cold (First Request) 1.850s 1.849s ▆▁▂▇▆
Warm (Listen) 456ms 456ms ▅▅▁▅▁
Warm (Ready in log) 435ms 439ms ▁▁▂▁▅
Warm (First Request) 1.864s 1.880s ▆▁▁▇▇

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.023s 4.022s █▃▂▇▄
Cached Build 4.032s 4.024s █▃▆█▅
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 14.480s 14.619s ▃▁▃▁▇
Cached Build 14.574s 14.589s ▄▃▄▃▆
node_modules Size 493 MB 493 MB ██▁██
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0-xemccipzit0.js gzip 13 kB N/A -
00-sfwc7hh0nr.js gzip 12.9 kB N/A -
031zhp2wrdez-.js gzip 154 B N/A -
04qa7l6zsfb_n.js gzip 160 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0gjc7ey0p325j.js gzip 156 B N/A -
0j53u7kin82u3.js gzip 155 B N/A -
0mfr9a8qgwu2-.js gzip 158 B N/A -
13_2qj6sfhcpr.js gzip 8.51 kB N/A -
13q15tdry7-jw.js gzip 9.81 kB N/A -
16jdy7mb2hpzo.js gzip 2.28 kB N/A -
16lhqjoqbznyg.js gzip 220 B 220 B
17c6iioxxyq_7.js gzip 8.51 kB N/A -
1e9hak60wi8_q.js gzip 10.1 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1lti_nqcijcmi.js gzip 225 B N/A -
1m9l9vnf18-38.js gzip 49 kB N/A -
1n1cbp2qs3c6_.js gzip 157 B N/A -
1npngsl9ov4pl.js gzip 157 B N/A -
1sxlusghwg9o8.js gzip 156 B N/A -
1xm_y25xq8s6f.js gzip 158 B N/A -
1zq04q8id1dsq.js gzip 8.59 kB N/A -
246le60fytek6.js gzip 1.46 kB N/A -
24a2dk89evk-t.js gzip 168 B N/A -
2ehaswxwdfkla.js gzip 157 B N/A -
2ipc3se0d9mja.js gzip 7.61 kB N/A -
2rm1ibbmjhlgi.js gzip 8.57 kB N/A -
3_jr5xwa3jsje.js gzip 159 B N/A -
30z650ayitjz3.js gzip 5.67 kB N/A -
33ur7c3w36-m-.js gzip 8.56 kB N/A -
396buwq-nlhir.js gzip 8.59 kB N/A -
39mk1fjm58e-4.js gzip 8.62 kB N/A -
3cq10epinkxrc.js gzip 450 B N/A -
3gbes1e_neq5r.js gzip 70.8 kB N/A -
3hpq82izpxxdb.js gzip 65.5 kB N/A -
3wc1tgfurjhi-.js gzip 9.23 kB N/A -
3ze9s70gat6n_.js gzip 8.56 kB N/A -
3zzpigmo8zmyz.js gzip 153 B N/A -
41obdnb4lqdgs.js gzip 13.3 kB N/A -
454bom347xpxj.js gzip 13.8 kB N/A -
457x5n-k0jr1x.js gzip 10.4 kB N/A -
turbopack-05..54pr.js gzip 4.15 kB N/A -
turbopack-0o.._qk5.js gzip 4.19 kB N/A -
turbopack-0o..wces.js gzip 4.17 kB N/A -
turbopack-1c..24y7.js gzip 4.18 kB N/A -
turbopack-1q..ih5c.js gzip 4.17 kB N/A -
turbopack-1v..3xg8.js gzip 4.17 kB N/A -
turbopack-20..756r.js gzip 4.17 kB N/A -
turbopack-22..ydlf.js gzip 4.17 kB N/A -
turbopack-29..1w0h.js gzip 4.17 kB N/A -
turbopack-2d..2qh7.js gzip 4.17 kB N/A -
turbopack-3_..y8jq.js gzip 4.17 kB N/A -
turbopack-35..y074.js gzip 4.17 kB N/A -
turbopack-37..rxtc.js gzip 4.17 kB N/A -
turbopack-3m..vc5x.js gzip 4.17 kB N/A -
0_1u_xrpzaeaj.js gzip N/A 8.52 kB -
0-5-yfd1ecntk.js gzip N/A 157 B -
0-ua_-urjvdtw.js gzip N/A 8.56 kB -
05_r_-_rf4w-n.js gzip N/A 7.61 kB -
0592wtqdkulqq.js gzip N/A 157 B -
0eihfygkvyao-.js gzip N/A 1.46 kB -
0g_88ua4o_jp-.js gzip N/A 9.24 kB -
0ma93fv-qiow4.js gzip N/A 157 B -
0nodmw6-hh5u6.js gzip N/A 156 B -
0ua91j3aes80c.js gzip N/A 8.58 kB -
0zwsxw6xkvw9p.js gzip N/A 8.62 kB -
1_fyx0hi94qc-.js gzip N/A 49 kB -
10mvvt3xn1_3j.js gzip N/A 8.59 kB -
1b75ishu64v5s.js gzip N/A 13 kB -
1b83ah3nflxjf.js gzip N/A 8.52 kB -
1dz40so46hch6.js gzip N/A 156 B -
1fd23spooi5r7.js gzip N/A 225 B -
1ftgbrikad2u6.js gzip N/A 170 B -
1nw99o32asytf.js gzip N/A 450 B -
1y6qa6xp0i1nz.js gzip N/A 13.3 kB -
21qijws6mkx9r.js gzip N/A 152 B -
2beo9pzpt3fl1.js gzip N/A 157 B -
2gkocby252aod.js gzip N/A 65.5 kB -
2kxdvc3gr7nt9.js gzip N/A 8.59 kB -
2sgg_sxyixu_p.js gzip N/A 13.8 kB -
2sk4gp5rmalb0.js gzip N/A 10.1 kB -
2u87ln5_zfir_.js gzip N/A 5.67 kB -
2z0hjofuwi3i4.js gzip N/A 70.8 kB -
30ml7bem4tp5g.js gzip N/A 162 B -
33602db0fe2xi.js gzip N/A 9.81 kB -
35ov1q3c2llta.js gzip N/A 158 B -
3bf_1fdjwxnjh.js gzip N/A 156 B -
3j3snr-ce7e0q.js gzip N/A 10.4 kB -
3padun1mj_-bl.js gzip N/A 160 B -
3rnlp1mlkg8yk.js gzip N/A 156 B -
3yby446qbgls0.js gzip N/A 8.56 kB -
3yypm2pwzx0mq.js gzip N/A 12.9 kB -
42lado0_6oegq.js gzip N/A 2.28 kB -
turbopack-04..nxo4.js gzip N/A 4.18 kB -
turbopack-0a..a77f.js gzip N/A 4.16 kB -
turbopack-1_..igdl.js gzip N/A 4.19 kB -
turbopack-1u..y2oj.js gzip N/A 4.18 kB -
turbopack-29..90al.js gzip N/A 4.18 kB -
turbopack-2e..7muw.js gzip N/A 4.18 kB -
turbopack-2s..er_u.js gzip N/A 4.18 kB -
turbopack-2s..l75_.js gzip N/A 4.18 kB -
turbopack-2u..ajpt.js gzip N/A 4.18 kB -
turbopack-2z..b0ig.js gzip N/A 4.18 kB -
turbopack-31..-t-q.js gzip N/A 4.18 kB -
turbopack-33..6gld.js gzip N/A 4.18 kB -
turbopack-3e..n1n_.js gzip N/A 4.18 kB -
turbopack-3i..ag0n.js gzip N/A 4.18 kB -
Total 464 kB 465 kB ⚠️ +133 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 716 B 719 B
Total 716 B 719 B ⚠️ +3 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 435 B 434 B
Total 435 B 434 B ✅ -1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
1011-HASH.js gzip 5.58 kB N/A -
2168.HASH.js gzip 169 B N/A -
2225-HASH.js gzip 4.64 kB N/A -
61a8f394-HASH.js gzip 62.8 kB N/A -
850-HASH.js gzip 60.6 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 255 B 251 B 🟢 4 B (-2%)
main-HASH.js gzip 39.3 kB 39.6 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
36c7d9a6-HASH.js gzip N/A 62.8 kB -
3967-HASH.js gzip N/A 4.63 kB -
5025-HASH.js gzip N/A 5.58 kB -
634-HASH.js gzip N/A 60.9 kB -
7586.HASH.js gzip N/A 170 B -
Total 235 kB 235 kB ⚠️ +609 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 194 B
_error-HASH.js gzip 182 B 181 B
css-HASH.js gzip 334 B 333 B
dynamic-HASH.js gzip 1.8 kB 1.81 kB
edge-ssr-HASH.js gzip 255 B 254 B
head-HASH.js gzip 352 B 352 B
hooks-HASH.js gzip 384 B 384 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 259 B 259 B
link-HASH.js gzip 2.52 kB 2.52 kB
routerDirect..HASH.js gzip 320 B 317 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.98 kB 7.99 kB ⚠️ +4 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 125 kB 126 kB
page.js gzip 273 kB 273 kB
Total 398 kB 399 kB ⚠️ +1.07 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 614 B 618 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 44.4 kB 44.2 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 46 kB 45.8 kB ✅ -222 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 719 B 718 B
Total 719 B 718 B ✅ -1 B
Build Cache
Canary PR Change
0.pack gzip 4.38 MB 4.38 MB
index.pack gzip 113 kB 115 kB 🔴 +1.96 kB (+2%)
index.pack.old gzip 114 kB 114 kB
Total 4.61 MB 4.61 MB ⚠️ +3.17 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 346 kB 346 kB
app-page-exp..prod.js gzip 192 kB 192 kB
app-page-tur...dev.js gzip 346 kB 346 kB
app-page-tur..prod.js gzip 191 kB 191 kB
app-page-tur...dev.js gzip 342 kB 342 kB
app-page-tur..prod.js gzip 189 kB 189 kB
app-page.run...dev.js gzip 342 kB 342 kB
app-page.run..prod.js gzip 190 kB 190 kB
app-route-ex...dev.js gzip 77 kB 77 kB
app-route-ex..prod.js gzip 52.5 kB 52.5 kB
app-route-tu...dev.js gzip 77 kB 77 kB
app-route-tu..prod.js gzip 52.5 kB 52.5 kB
app-route-tu...dev.js gzip 76.6 kB 76.6 kB
app-route-tu..prod.js gzip 52.2 kB 52.2 kB
app-route.ru...dev.js gzip 76.6 kB 76.6 kB
app-route.ru..prod.js gzip 52.2 kB 52.2 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 43.9 kB 43.9 kB
pages-api-tu..prod.js gzip 33.5 kB 33.5 kB
pages-api.ru...dev.js gzip 43.9 kB 43.9 kB
pages-api.ru..prod.js gzip 33.4 kB 33.4 kB
pages-turbo....dev.js gzip 53.3 kB 53.3 kB
pages-turbo...prod.js gzip 39.1 kB 39.1 kB
pages.runtim...dev.js gzip 53.3 kB 53.3 kB
pages.runtim..prod.js gzip 39 kB 39 kB
server.runti..prod.js gzip 62.9 kB 62.9 kB
Total 3.06 MB 3.06 MB ✅ -3 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/27674179cf557ca262ce254b752f81b5198eee65/next

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Next.js team PRs by the Next.js team. Documentation Related to Next.js' official documentation. tests Turbopack Related to Turbopack with Next.js. type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants