Skip to content

Serve ISR fallback shells in response to prefetch requests#94534

Draft
acdlite wants to merge 1 commit into
canaryfrom
serve-isr-fallback-on-prefetch
Draft

Serve ISR fallback shells in response to prefetch requests#94534
acdlite wants to merge 1 commit into
canaryfrom
serve-isr-fallback-on-prefetch

Conversation

@acdlite
Copy link
Copy Markdown
Contributor

@acdlite acdlite commented Jun 7, 2026

Previously, a static segment prefetch that hit a route with an unresolved ISR entry could not be served a fallback shell, so the prefetch cache was left cold until the entry regenerated. This enables prefetch requests to be served the ISR fallback shell immediately, so the prefetch cache can be warmed right away instead of waiting.

Because the shell is only a partial response, the client retries the prefetch a bounded number of times so it eventually warms the cache with the full (concrete) response once the server finishes regenerating in the background. Only shells that can actually be upgraded are retried — a route with no generateStaticParams never upgrades, so its shell isn't flagged and the client doesn't waste retries on it.

The new serving behavior is gated behind the experimental appShells flag, so existing behavior is unchanged when it's off.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

Failing test suites

Commit: 0add1b2 | About building and testing Next.js

pnpm test-dev test/development/app-dir/hmr-intercept-routes/hmr-intercept-routes.test.ts (job)

  • hmr-intercept-routes > should update intercept routes via HMR (DD)
Expand output

● hmr-intercept-routes › should update intercept routes via HMR

page.waitForSelector: Timeout 10000ms exceeded.
Call log:
  - waiting for locator('#default-intercept') to be visible

  560 |
  561 |     return this.startChain(async () => {
> 562 |       const el = await page.waitForSelector(selector, {
      |                             ^
  563 |         timeout,
  564 |         state,
  565 |       })

  at waitForSelector (lib/browsers/playwright.ts:562:29)
  at Playwright._chain (lib/browsers/playwright.ts:692:23)
  at Playwright._chain [as startChain] (lib/browsers/playwright.ts:673:17)
  at Playwright.startChain [as waitForElementByCss] (lib/browsers/playwright.ts:561:17)
  at Object.waitForElementByCss (development/app-dir/hmr-intercept-routes/hmr-intercept-routes.test.ts:38:21)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

Stats from current PR

🔴 1 regression

Metric Canary PR Change Trend
node_modules Size 510 MB 511 MB 🔴 +591 kB (+0%) ▁▁▁▁█
📊 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) 815ms 812ms ████▁
Cold (Ready in log) 818ms 795ms █▇██▁
Cold (First Request) 1.345s 1.310s ▇▆▇▇▁
Warm (Listen) 813ms 812ms ████▁
Warm (Ready in log) 806ms 800ms █▇██▁
Warm (First Request) 642ms 633ms ▇▅▇▇▁
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 709ms 708ms ████▁
Cold (Ready in log) 705ms 702ms ████▁
Cold (First Request) 2.972s 2.982s ███▇▁
Warm (Listen) 709ms 709ms ████▁
Warm (Ready in log) 702ms 706ms ████▁
Warm (First Request) 2.982s 2.983s ███▇▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 5.418s 5.387s ▇▇▇█▁
Cached Build 5.405s 5.342s ▇▇██▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 23.005s 22.783s ███▇▁
Cached Build 22.823s 22.781s ████▁
node_modules Size 510 MB 511 MB 🔴 +591 kB (+0%) ▁▁▁▁█
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0_mmk-hpeq702.js gzip 13.6 kB N/A -
014m8tx_ynmmb.js gzip 157 B N/A -
01n68n8hhw5d_.js gzip 13.1 kB N/A -
091lqj7mftx18.js gzip 9.44 kB N/A -
0bb0vtp8dwsez.js gzip 450 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0e0ix5ayu7lzk.js gzip 152 B N/A -
0et1w1og4qe-w.js gzip 8.77 kB N/A -
0gka33kfmylew.js gzip 5.73 kB N/A -
0jxric53p6_y_.js gzip 71 kB N/A -
0nxqz3q165m2l.js gzip 220 B N/A -
0ovsr4n3xpaqs.js gzip 154 B N/A -
106k2vntvwwyq.js gzip 8.71 kB N/A -
15aruh8j7i36l.js gzip 51.4 kB N/A -
17h18zxt1915l.js gzip 1.46 kB N/A -
19f-38ee1pg1h.js gzip 10.6 kB N/A -
19r7vnzv1m3sb.js gzip 156 B N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1jrysmkaprf3-.js gzip 153 B N/A -
1mpn9k5m63u2c.js gzip 8.82 kB N/A -
1qu0uhree4e81.js gzip 163 B N/A -
1um8hdpe4i7pb.js gzip 153 B N/A -
1zuz4qcpo6_iw.js gzip 233 B N/A -
26xvqnur0qzto.js gzip 2.29 kB N/A -
2cig-t0drayp7.js gzip 8.78 kB N/A -
2f26of93kwaq2.js gzip 8.71 kB N/A -
2k12tfw0itdpg.js gzip 157 B N/A -
2puwozfp1b6z1.js gzip 8.77 kB N/A -
2taxvkjo_52n1.js gzip 13 kB N/A -
2ugfqzz6jnlhg.js gzip 10.3 kB N/A -
2w_tv3ndkgyn0.js gzip 8.79 kB N/A -
2xmghrvpgur3c.js gzip 65.6 kB N/A -
32148fe-awzqh.js gzip 167 B N/A -
34sv9yx51zdzn.js gzip 10 kB N/A -
3832jw9t4kfff.js gzip 8.79 kB N/A -
3e0r7kpij1ka9.js gzip 157 B N/A -
3l2-s66d983z-.js gzip 7.61 kB N/A -
3uzr-bn1ntam1.js gzip 154 B N/A -
3vj9u_3qs3jqn.js gzip 13.9 kB N/A -
3w8-acy6fn2xf.js gzip 155 B N/A -
3wk-7uxoz7wue.js gzip 155 B N/A -
turbopack-0c..7xmk.js gzip 3.63 kB N/A -
turbopack-0v..l8n0.js gzip 3.63 kB N/A -
turbopack-11..okmd.js gzip 3.63 kB N/A -
turbopack-12..q3dc.js gzip 3.63 kB N/A -
turbopack-1a..el4w.js gzip 3.63 kB N/A -
turbopack-1u..fpoa.js gzip 3.62 kB N/A -
turbopack-2b..g9dc.js gzip 3.63 kB N/A -
turbopack-2b..1_aj.js gzip 3.63 kB N/A -
turbopack-2j..v7h8.js gzip 3.63 kB N/A -
turbopack-2t..e3fy.js gzip 3.61 kB N/A -
turbopack-2u..5xmb.js gzip 3.64 kB N/A -
turbopack-34..thio.js gzip 3.63 kB N/A -
turbopack-3x..an3p.js gzip 3.63 kB N/A -
turbopack-3z..pxav.js gzip 3.63 kB N/A -
0-2ztv9rd3p0y.js gzip N/A 151 B -
00qp36i96c7wm.js gzip N/A 154 B -
00tc16j5-eos9.js gzip N/A 1.46 kB -
09zhpvggrs_ox.js gzip N/A 13.9 kB -
0cwfg7a8dugo8.js gzip N/A 154 B -
0ftf54asi5ajl.js gzip N/A 9.43 kB -
0j-dsdvj1vveo.js gzip N/A 8.76 kB -
0w0qlyuve48o0.js gzip N/A 71 kB -
16m0omygzybhx.js gzip N/A 155 B -
170t10ub162wk.js gzip N/A 5.74 kB -
1bphlaoc0oqjw.js gzip N/A 8.78 kB -
1e5p7c65hhjno.js gzip N/A 8.71 kB -
1kon91zndf-mv.js gzip N/A 8.79 kB -
1llqf2qgac_-d.js gzip N/A 8.7 kB -
1lphldovrrilz.js gzip N/A 8.77 kB -
1ozvxybf0pgkc.js gzip N/A 65.6 kB -
22geiudvi1dh6.js gzip N/A 2.29 kB -
239s_zo5pp9-s.js gzip N/A 51.7 kB -
23p7wy6zx0abb.js gzip N/A 10.3 kB -
2a55t4ptxrixc.js gzip N/A 157 B -
2e-j3wnb1--6p.js gzip N/A 159 B -
2ee39g8qw0kdi.js gzip N/A 10 kB -
2j007o3k44oed.js gzip N/A 8.76 kB -
2pjm3adipohnp.js gzip N/A 168 B -
2vhmp74csw0ql.js gzip N/A 232 B -
2vn0l6vcof8b6.js gzip N/A 8.81 kB -
2zvpwewixhlo4.js gzip N/A 154 B -
30h_m0irj-q6p.js gzip N/A 221 B -
33-mzojzjvkog.js gzip N/A 155 B -
337lru665p0im.js gzip N/A 160 B -
35a2y97wsxzak.js gzip N/A 157 B -
38c63ct4i1cy7.js gzip N/A 449 B -
39kshdfkb5_dc.js gzip N/A 12.9 kB -
3ba5d7re6m3_n.js gzip N/A 157 B -
3bxtggll2q95f.js gzip N/A 13.1 kB -
3hgl8vud2c1f4.js gzip N/A 155 B -
3kzc9t3aw7glu.js gzip N/A 7.61 kB -
3xjvvbpazem9e.js gzip N/A 10.6 kB -
438815bhbzqil.js gzip N/A 13.6 kB -
turbopack-0d..j37o.js gzip N/A 3.62 kB -
turbopack-0e..u9_j.js gzip N/A 3.63 kB -
turbopack-0q..0p11.js gzip N/A 3.63 kB -
turbopack-16..rdy6.js gzip N/A 3.63 kB -
turbopack-1f..xhzf.js gzip N/A 3.64 kB -
turbopack-1g..p8z1.js gzip N/A 3.62 kB -
turbopack-1w..71gr.js gzip N/A 3.63 kB -
turbopack-2i..4zn5.js gzip N/A 3.63 kB -
turbopack-2p..nzbv.js gzip N/A 3.63 kB -
turbopack-2s..w5d_.js gzip N/A 3.63 kB -
turbopack-2z..lt5o.js gzip N/A 3.62 kB -
turbopack-34..kb3-.js gzip N/A 3.63 kB -
turbopack-3o..ewhg.js gzip N/A 3.61 kB -
turbopack-43..tchg.js gzip N/A 3.63 kB -
Total 462 kB 462 kB ⚠️ +170 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 727 B 728 B
Total 727 B 728 B ⚠️ +1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 432 B 432 B
Total 432 B 432 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2486.HASH.js gzip 169 B N/A -
3146-HASH.js gzip 61.7 kB N/A -
39fcf99b-HASH.js gzip 62.8 kB N/A -
8443-HASH.js gzip 4.69 kB N/A -
9431-HASH.js gzip 5.63 kB N/A -
framework-HASH.js gzip 59.8 kB 59.8 kB
main-app-HASH.js gzip 256 B 253 B 🟢 3 B (-1%)
main-HASH.js gzip 39.4 kB 39.8 kB 🔴 +432 B (+1%)
webpack-HASH.js gzip 1.68 kB 1.68 kB
6105-HASH.js gzip N/A 5.64 kB -
764.HASH.js gzip N/A 169 B -
8898-HASH.js gzip N/A 61.4 kB -
9597-HASH.js gzip N/A 4.67 kB -
e1ccab69-HASH.js gzip N/A 62.8 kB -
Total 236 kB 236 kB ⚠️ +104 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 183 B 182 B
css-HASH.js gzip 335 B 335 B
dynamic-HASH.js gzip 1.8 kB 1.8 kB
edge-ssr-HASH.js gzip 255 B 254 B
head-HASH.js gzip 351 B 349 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.53 kB 2.52 kB
routerDirect..HASH.js gzip 319 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 313 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.99 kB 7.99 kB ✅ -7 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 279 kB 280 kB
Total 405 kB 406 kB ⚠️ +1.29 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 616 B 615 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 44.9 kB 44.8 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 46.5 kB 46.4 kB ✅ -179 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 719 B 717 B
Total 719 B 717 B ✅ -2 B
Build Cache
Canary PR Change
0.pack gzip 4.54 MB 4.57 MB 🔴 +24.3 kB (+1%)
index.pack gzip 114 kB 114 kB
index.pack.old gzip 114 kB 115 kB
Total 4.77 MB 4.8 MB ⚠️ +24.7 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 357 kB 357 kB
app-page-exp..prod.js gzip 198 kB 198 kB
app-page-tur...dev.js gzip 356 kB 357 kB
app-page-tur..prod.js gzip 198 kB 198 kB
app-page-tur...dev.js gzip 353 kB 353 kB
app-page-tur..prod.js gzip 196 kB 196 kB
app-page.run...dev.js gzip 353 kB 354 kB
app-page.run..prod.js gzip 196 kB 196 kB
app-route-ex...dev.js gzip 77.8 kB 77.9 kB
app-route-ex..prod.js gzip 53.1 kB 53.1 kB
app-route-tu...dev.js gzip 77.8 kB 77.9 kB
app-route-tu..prod.js gzip 53.1 kB 53.2 kB
app-route-tu...dev.js gzip 77.4 kB 77.5 kB
app-route-tu..prod.js gzip 52.9 kB 52.9 kB
app-route.ru...dev.js gzip 77.4 kB 77.5 kB
app-route.ru..prod.js gzip 52.8 kB 52.9 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 44.3 kB 44.4 kB
pages-api-tu..prod.js gzip 33.8 kB 33.8 kB
pages-api.ru...dev.js gzip 44.3 kB 44.4 kB
pages-api.ru..prod.js gzip 33.7 kB 33.8 kB
pages-turbo....dev.js gzip 53.7 kB 53.8 kB
pages-turbo...prod.js gzip 39.4 kB 39.5 kB
pages.runtim...dev.js gzip 53.6 kB 53.7 kB
pages.runtim..prod.js gzip 39.4 kB 39.4 kB
server.runti..prod.js gzip 63.4 kB 63.4 kB
use-cache-pr...dev.js gzip 70.6 kB 70.6 kB
use-cache-pr...dev.js gzip 70.7 kB 70.7 kB
use-cache-pr...dev.js gzip 69 kB 69 kB
use-cache-pr...dev.js gzip 69 kB 69 kB
Total 3.41 MB 3.42 MB ⚠️ +5.04 kB
📝 Changed Files (24 files)

Files with changes:

  • app-page-exp..ntime.dev.js
  • app-page-exp..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page.runtime.dev.js
  • app-page.runtime.prod.js
  • app-route-ex..ntime.dev.js
  • app-route-ex..time.prod.js
  • app-route-tu..ntime.dev.js
  • app-route-tu..time.prod.js
  • app-route-tu..ntime.dev.js
  • app-route-tu..time.prod.js
  • app-route.runtime.dev.js
  • app-route.ru..time.prod.js
  • pages-api-tu..ntime.dev.js
  • pages-api-tu..time.prod.js
  • pages-api.runtime.dev.js
  • pages-api.ru..time.prod.js
  • ... and 4 more
View diffs
app-page-exp..ntime.dev.js
failed to diff
app-page-exp..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js

Diff too large to display

app-page.runtime.dev.js
failed to diff
app-page.runtime.prod.js

Diff too large to display

app-route-ex..ntime.dev.js

Diff too large to display

app-route-ex..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route.runtime.dev.js

Diff too large to display

app-route.ru..time.prod.js

Diff too large to display

pages-api-tu..ntime.dev.js

Diff too large to display

pages-api-tu..time.prod.js

Diff too large to display

pages-api.runtime.dev.js

Diff too large to display

pages-api.ru..time.prod.js

Diff too large to display

pages-turbo...ntime.dev.js

Diff too large to display

pages-turbo...time.prod.js

Diff too large to display

pages.runtime.dev.js

Diff too large to display

pages.runtime.prod.js

Diff too large to display

📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/0add1b211f01e6f341be8061f160d77891587ed1/next

Commit: 0add1b2

Previously, a static segment prefetch that hit a route with an
unresolved ISR entry could not be served a fallback shell, so the
prefetch cache was left cold until the entry regenerated. This
enables prefetch requests to be served the ISR fallback shell
immediately, so the prefetch cache can be warmed right away instead
of waiting.

Because the shell is only a partial response, the client retries the
prefetch a bounded number of times so it eventually warms the cache
with the full (concrete) response once the server finishes
regenerating in the background. Only shells that can actually be
upgraded are retried — a route with no generateStaticParams never
upgrades, so its shell isn't flagged and the client doesn't waste
retries on it.

The new serving behavior is gated behind the experimental `appShells`
flag, so existing behavior is unchanged when it's off.
@acdlite acdlite force-pushed the serve-isr-fallback-on-prefetch branch from 8f8f5ea to 0add1b2 Compare June 7, 2026 06:47
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.

1 participant