Skip to content

fetch: respect HTTP Cache-Control headers with TTL-based invalidation (#91729)#93228

Merged
lukesandberg merged 6 commits into
canaryfrom
respect_http_cache_control_headers_for_fetches_v2
May 14, 2026
Merged

fetch: respect HTTP Cache-Control headers with TTL-based invalidation (#91729)#93228
lukesandberg merged 6 commits into
canaryfrom
respect_http_cache_control_headers_for_fetches_v2

Conversation

@lukesandberg
Copy link
Copy Markdown
Contributor

@lukesandberg lukesandberg commented Apr 24, 2026

Summary

This is a re-application of the HTTP fetch part of #91729. Stacked on #93227.

Fix fetch to respect HTTP Cache-Control headers

Previously, fetch results were cached indefinitely, meaning results would never be refreshed (unless the cache was invalidated). Now they are session_dependent with a TTL to ensure we respect HTTP cache settings (e.g. Google Fonts with max-age=86400).

New two-task pattern:

  • fetch_inner (NOT session_dependent): Performs the HTTP request, grabs an Invalidator for itself, and returns the response + invalidator + absolute deadline. Cached across sessions.
  • fetch (network, session_dependent): Reads the cached fetch_inner result and spawns a timer to invalidate when the TTL expires.

On warm cache restore, fetch re-executes (session-dependent), reads the persisted deadline from fetch_inner's cached result, computes remaining TTL, and spawns a timer — no HTTP request unless the TTL has already expired. Mid-session, the timer fires and triggers a re-fetch.

Error handling: On fetch failure, fetch_inner takes a dependency on Completion::session_dependent() so transient errors (network down, DNS failure) are retried on the next session without busy-looping.

Test Plan

3 new integration tests in turbo-tasks-fetch/tests/fetch.rs:

  • ttl_invalidates_within_session — mock server returns max-age=1, body changes, verifies re-fetch after TTL
  • ttl_invalidates_on_session_restore — fetches with TTL, stops TT, waits for expiry, warm restores with new TT, verifies re-fetch
  • errors_retried_on_session_restore — server returns 500, stops TT, fixes server, warm restores, verifies success
  • Existing 6 fetch tests continue to pass

Copy link
Copy Markdown
Contributor Author

lukesandberg commented Apr 24, 2026

@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Apr 24, 2026
@lukesandberg lukesandberg reopened this Apr 24, 2026
@lukesandberg lukesandberg force-pushed the re_apply_session_dependent_tasks_v2 branch from 6ed8c8c to 0d4133c Compare April 24, 2026 23:57
@lukesandberg lukesandberg force-pushed the respect_http_cache_control_headers_for_fetches_v2 branch 2 times, most recently from 84cc1af to e5ee47b Compare April 25, 2026 00:25
@lukesandberg lukesandberg force-pushed the re_apply_session_dependent_tasks_v2 branch 2 times, most recently from 4d4ba2e to 8dfbea9 Compare April 25, 2026 00:27
@lukesandberg lukesandberg force-pushed the respect_http_cache_control_headers_for_fetches_v2 branch from e5ee47b to 09e7e0e Compare April 25, 2026 00:27
#[turbo_tasks::function(operation)]
async fn fetch_body(url: RcStr) -> Result<Vc<RcStr>> {
let client_vc = FetchClientConfig {
min_cache_control_secs: 0,
Copy link
Copy Markdown
Contributor

@vercel vercel Bot Apr 25, 2026

Choose a reason for hiding this comment

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

Test file uses the old field name min_cache_control_secs: 0 which was renamed to min_cache_control: Duration in the struct definition, causing a compilation error.

Fix on Vercel

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 25, 2026

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) 812ms 813ms ▄▁▅▄▄
Cold (Ready in log) 785ms 786ms ▆▃▆▆▂
Cold (First Request) 1.239s 1.249s ▅▃▆▆▂
Warm (Listen) 813ms 812ms ▃▃▃▆▁
Warm (Ready in log) 787ms 786ms █▄██▃
Warm (First Request) 594ms 594ms █▅██▄
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 811ms 811ms ▃▆▃▃▃
Cold (Ready in log) 779ms 778ms ▄▃█▅▂
Cold (First Request) 3.224s 3.193s ▄▃▅▃▂
Warm (Listen) 813ms 811ms ▁▅▁▁▅
Warm (Ready in log) 778ms 777ms ▄▂▇▄▂
Warm (First Request) 3.239s 3.227s ▄▃▆▄▂

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.825s 4.750s ▄▅▇█▃
Cached Build 4.771s 4.828s ▄▄▄█▂
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 23.958s 24.024s ▁▄▃▁▄
Cached Build 24.041s 23.881s ▂▅▄▁▄
node_modules Size 506 MB 506 MB ▁▁▁▁▁
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0-_5zcp4erz06.js gzip 155 B N/A -
04hm05ar7kldw.js gzip 5.73 kB N/A -
0c2d37sbzdihx.js gzip 70.9 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0dvitrl5zg37g.js gzip 8.82 kB N/A -
0sf7ysou-72zd.js gzip 8.71 kB N/A -
157abun3hwc_s.js gzip 10.3 kB N/A -
17x5h-qqxhzrx.js gzip 154 B N/A -
1a87ez4a5ecgj.js gzip 156 B N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1g3whbu20hkn7.js gzip 155 B N/A -
1jj68jv9537mc.js gzip 13.8 kB N/A -
1jpaub6y8xlfr.js gzip 2.3 kB N/A -
1ot0mvscrc_uf.js gzip 233 B N/A -
1xu8iw0j0r_tg.js gzip 151 B N/A -
1z3ut8arlyv-i.js gzip 160 B N/A -
1zjhxeflzl-vy.js gzip 159 B N/A -
2_m3xv2uq3sjc.js gzip 1.46 kB N/A -
24qtz2uzl4wst.js gzip 155 B N/A -
24y34mwgrkqp4.js gzip 8.78 kB N/A -
2b9eovwn8litd.js gzip 167 B N/A -
2c-fd4y1zozz8.js gzip 8.79 kB N/A -
2d7416h_xd36x.js gzip 8.71 kB N/A -
2g21ny1t2kw37.js gzip 7.61 kB N/A -
2lyuhit6rn8fy.js gzip 9.44 kB N/A -
2m04xa7h1rs-q.js gzip 154 B N/A -
2q0gr8wfr3jwl.js gzip 8.77 kB N/A -
2slg72_yqs010.js gzip 153 B N/A -
2t9e75oz6r0zp.js gzip 8.76 kB N/A -
2uku_olcn15b7.js gzip 8.79 kB N/A -
30r8mm-46bdqy.js gzip 220 B 220 B
3c1jdxkzlb8oq.js gzip 12.9 kB N/A -
3dyld9d_xsqrn.js gzip 49.9 kB N/A -
3inab2jybr4k9.js gzip 450 B N/A -
3jkm5tdjvaf_q.js gzip 13.1 kB N/A -
3lo6ux8llsnmh.js gzip 155 B N/A -
3mt67agm5wp40.js gzip 10.6 kB N/A -
3rssl3skxqw0e.js gzip 65.6 kB N/A -
3saabek4kohwi.js gzip 10 kB N/A -
3xx6s_ieg68am.js gzip 155 B N/A -
4189xmby9yu1p.js gzip 13.6 kB N/A -
turbopack-03..4q7w.js gzip 4.21 kB N/A -
turbopack-1g..3ss7.js gzip 4.2 kB N/A -
turbopack-1q..-ok_.js gzip 4.2 kB N/A -
turbopack-22..xlgf.js gzip 4.2 kB N/A -
turbopack-2j..-i08.js gzip 4.2 kB N/A -
turbopack-2m..lvl1.js gzip 4.19 kB N/A -
turbopack-2n..6oym.js gzip 4.2 kB N/A -
turbopack-2q..ly21.js gzip 4.2 kB N/A -
turbopack-2z..p4tc.js gzip 4.2 kB N/A -
turbopack-2z..7_r3.js gzip 4.2 kB N/A -
turbopack-36..6bnx.js gzip 4.2 kB N/A -
turbopack-37..j9ni.js gzip 4.18 kB N/A -
turbopack-3h..ye_7.js gzip 4.2 kB N/A -
turbopack-3u..zpvr.js gzip 4.2 kB N/A -
0_i7nqgx23st7.js gzip N/A 10 kB -
04ecz5id4y-83.js gzip N/A 49.9 kB -
06puhytyxk31p.js gzip N/A 8.82 kB -
0fi0ogq0bwa6w.js gzip N/A 156 B -
0fuas45e3_c21.js gzip N/A 155 B -
0inuwrr_z3vqy.js gzip N/A 157 B -
0j42f9zonj0wd.js gzip N/A 13 kB -
0m34gln_kt4fg.js gzip N/A 5.73 kB -
0sy8u1vi8lp5q.js gzip N/A 160 B -
0tw7eybgj56wj.js gzip N/A 162 B -
1c9f_65chmta6.js gzip N/A 152 B -
1g3q1ww01thnl.js gzip N/A 2.3 kB -
1hraqxuiymq6v.js gzip N/A 8.79 kB -
1l9un1sl77287.js gzip N/A 1.46 kB -
1m4eug5ntv_6s.js gzip N/A 158 B -
21-eavqb1k_36.js gzip N/A 13.9 kB -
2147zgtf14z-q.js gzip N/A 234 B -
23bz3xsg-5-1s.js gzip N/A 8.71 kB -
27441mytv7pbm.js gzip N/A 9.43 kB -
29glh04g2fky_.js gzip N/A 155 B -
2bvt07mcvgwk5.js gzip N/A 70.9 kB -
2cjkwjgm1zcfs.js gzip N/A 8.71 kB -
2juhakbhezws1.js gzip N/A 155 B -
2nvrrw4th6joh.js gzip N/A 156 B -
2scd8zaoyb8md.js gzip N/A 8.79 kB -
2st_qs6p_9us0.js gzip N/A 13.1 kB -
2yjp27e2w0jqk.js gzip N/A 157 B -
2zo2exm1d8qj1.js gzip N/A 13.6 kB -
3_3acx7-vxc4d.js gzip N/A 170 B -
38d-gyinlsucy.js gzip N/A 155 B -
3ehsq7o481snb.js gzip N/A 65.6 kB -
3f710q6kll2xn.js gzip N/A 7.61 kB -
3hn75zuxly9az.js gzip N/A 10.3 kB -
3hqh7m128tvsn.js gzip N/A 8.77 kB -
3hqti_t-zy1x4.js gzip N/A 449 B -
3mnawenie1flm.js gzip N/A 8.76 kB -
3ubsozlu6zs38.js gzip N/A 10.6 kB -
43iwfqjnx1cy_.js gzip N/A 8.78 kB -
turbopack-05..7ncy.js gzip N/A 4.2 kB -
turbopack-0b..v1vw.js gzip N/A 4.2 kB -
turbopack-16..nnhn.js gzip N/A 4.18 kB -
turbopack-1f..ft9l.js gzip N/A 4.21 kB -
turbopack-1r..0jem.js gzip N/A 4.2 kB -
turbopack-1x..m7up.js gzip N/A 4.2 kB -
turbopack-22..gj3o.js gzip N/A 4.2 kB -
turbopack-23..a1fe.js gzip N/A 4.2 kB -
turbopack-2a..bl3v.js gzip N/A 4.2 kB -
turbopack-2c..vpvm.js gzip N/A 4.2 kB -
turbopack-2v..o277.js gzip N/A 4.2 kB -
turbopack-3-..eqbs.js gzip N/A 4.2 kB -
turbopack-3k..-v4v.js gzip N/A 4.2 kB -
turbopack-3n..qcfi.js gzip N/A 4.2 kB -
Total 469 kB 469 kB ⚠️ +113 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 716 B 721 B
Total 716 B 721 B ⚠️ +5 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 432 B 433 B
Total 432 B 433 B ⚠️ +1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2258-HASH.js gzip 61.4 kB N/A -
2266-HASH.js gzip 4.69 kB N/A -
3317.HASH.js gzip 169 B N/A -
4866-HASH.js gzip 5.64 kB N/A -
9e302639-HASH.js gzip 62.8 kB N/A -
framework-HASH.js gzip 59.5 kB 59.5 kB
main-app-HASH.js gzip 256 B 255 B
main-HASH.js gzip 39.9 kB 39.9 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
175fd0fd-HASH.js gzip N/A 62.8 kB -
2596-HASH.js gzip N/A 5.63 kB -
34-HASH.js gzip N/A 61.3 kB -
5691.HASH.js gzip N/A 169 B -
9156-HASH.js gzip N/A 4.68 kB -
Total 236 kB 236 kB ✅ -100 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 193 B 193 B
_error-HASH.js gzip 181 B 182 B
css-HASH.js gzip 334 B 332 B
dynamic-HASH.js gzip 1.79 kB 1.81 kB
edge-ssr-HASH.js gzip 255 B 255 B
head-HASH.js gzip 351 B 348 B
hooks-HASH.js gzip 385 B 384 B
image-HASH.js gzip 580 B 580 B
index-HASH.js gzip 257 B 259 B
link-HASH.js gzip 2.51 kB 2.52 kB
routerDirect..HASH.js gzip 318 B 319 B
script-HASH.js gzip 387 B 386 B
withRouter-HASH.js gzip 316 B 316 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.99 kB ⚠️ +19 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 275 kB 270 kB 🟢 5.32 kB (-2%)
Total 401 kB 396 kB ✅ -5.53 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 620 B 613 B 🟢 7 B (-1%)
middleware-r..fest.js gzip 155 B 155 B
middleware.js gzip 44.4 kB 44.9 kB 🔴 +498 B (+1%)
edge-runtime..pack.js gzip 842 B 842 B
Total 46 kB 46.5 kB ⚠️ +491 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.48 MB 4.47 MB 🟢 5.3 kB (0%)
index.pack gzip 115 kB 115 kB
index.pack.old gzip 116 kB 114 kB 🟢 2.2 kB (-2%)
Total 4.71 MB 4.7 MB ✅ -7.47 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 350 kB 350 kB
app-page-exp..prod.js gzip 194 kB 194 kB
app-page-tur...dev.js gzip 349 kB 349 kB
app-page-tur..prod.js gzip 194 kB 194 kB
app-page-tur...dev.js gzip 346 kB 346 kB
app-page-tur..prod.js gzip 192 kB 192 kB
app-page.run...dev.js gzip 346 kB 346 kB
app-page.run..prod.js gzip 192 kB 192 kB
app-route-ex...dev.js gzip 77.5 kB 77.5 kB
app-route-ex..prod.js gzip 52.9 kB 52.9 kB
app-route-tu...dev.js gzip 77.6 kB 77.6 kB
app-route-tu..prod.js gzip 52.9 kB 52.9 kB
app-route-tu...dev.js gzip 77.2 kB 77.2 kB
app-route-tu..prod.js gzip 52.7 kB 52.7 kB
app-route.ru...dev.js gzip 77.1 kB 77.1 kB
app-route.ru..prod.js gzip 52.7 kB 52.7 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.3 kB
pages-api-tu..prod.js gzip 33.8 kB 33.8 kB
pages-api.ru...dev.js gzip 44.3 kB 44.3 kB
pages-api.ru..prod.js gzip 33.7 kB 33.7 kB
pages-turbo....dev.js gzip 53.7 kB 53.7 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 53.6 kB 53.6 kB
pages.runtim..prod.js gzip 39.4 kB 39.4 kB
server.runti..prod.js gzip 63.1 kB 63.1 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 68 kB 68 kB
use-cache-pr...dev.js gzip 68 kB 68 kB
Total 3.37 MB 3.37 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/5357124d6fe40c4b5d5d0a8fb8e34a9c28ccd72d/next

Commit: 5357124

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 25, 2026

Tests Passed

Commit: 5357124

@lukesandberg lukesandberg force-pushed the re_apply_session_dependent_tasks_v2 branch from 8dfbea9 to af1f451 Compare April 25, 2026 01:15
@lukesandberg lukesandberg force-pushed the respect_http_cache_control_headers_for_fetches_v2 branch from 09e7e0e to 11516ac Compare April 25, 2026 01:15
@lukesandberg lukesandberg force-pushed the respect_http_cache_control_headers_for_fetches_v2 branch from 11516ac to f609aa2 Compare May 2, 2026 05:54
@lukesandberg lukesandberg force-pushed the re_apply_session_dependent_tasks_v2 branch from af1f451 to c326a28 Compare May 2, 2026 05:54
@lukesandberg lukesandberg marked this pull request as ready for review May 2, 2026 05:54
@lukesandberg lukesandberg requested a review from a team May 2, 2026 05:54
@lukesandberg lukesandberg force-pushed the respect_http_cache_control_headers_for_fetches_v2 branch from 90a5e99 to 588fe96 Compare May 13, 2026 07:19
@lukesandberg lukesandberg force-pushed the re_apply_session_dependent_tasks_v2 branch from c326a28 to 13be82f Compare May 13, 2026 07:19
Base automatically changed from re_apply_session_dependent_tasks_v2 to canary May 13, 2026 14:25
@lukesandberg lukesandberg force-pushed the respect_http_cache_control_headers_for_fetches_v2 branch from 588fe96 to df11034 Compare May 13, 2026 15:36
Comment thread turbopack/crates/turbo-tasks-fetch/src/client.rs Outdated
fewer braces
@lukesandberg lukesandberg enabled auto-merge (squash) May 13, 2026 23:39
@lukesandberg lukesandberg merged commit 2e90936 into canary May 14, 2026
164 checks passed
@lukesandberg lukesandberg deleted the respect_http_cache_control_headers_for_fetches_v2 branch May 14, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants