@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -93,8 +93,8 @@
93
93
  */
94
94
  import type { ResolvedSegment } from "../../types.js";
95
95
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
96
- import { getRouterContext } from "../router-context.js";
97
- import { resolveSink, safeEmit } from "../telemetry.js";
96
+ import { getRouterContext, type RouterContext } from "../router-context.js";
97
+ import { observeEvent } from "../instrument.js";
98
98
  import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
99
99
  import { treeHasStreaming } from "./segment-resolution.js";
100
100
  import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
@@ -103,6 +103,7 @@ import {
103
103
  getRequestContext,
104
104
  _getRequestContext,
105
105
  } from "../../server/request-context.js";
106
+ import { paramsEqual } from "../params-util.js";
106
107
 
107
108
  // Lazily initialized prerender store singleton and dynamically imported deps.
108
109
  // Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
@@ -114,6 +115,9 @@ let _deserializeSegments:
114
115
  let _restoreHandles:
115
116
  | typeof import("../../cache/handle-snapshot.js").restoreHandles
116
117
  | undefined;
118
+ let _decodeHandles:
119
+ | typeof import("../../cache/handle-snapshot.js").decodeHandles
120
+ | undefined;
117
121
  let _hashParams:
118
122
  | typeof import("../../prerender/param-hash.js").hashParams
119
123
  | undefined;
@@ -121,22 +125,6 @@ let _lazyGetRequestContext:
121
125
  | typeof import("../../server/request-context.js").getRequestContext
122
126
  | undefined;
123
127
 
124
- function paramsEqual(
125
- a: Record<string, string>,
126
- b: Record<string, string>,
127
- ): boolean {
128
- if (a === b) return true;
129
-
130
- const keysA = Object.keys(a);
131
- if (keysA.length !== Object.keys(b).length) return false;
132
-
133
- for (const key of keysA) {
134
- if (a[key] !== b[key]) return false;
135
- }
136
-
137
- return true;
138
- }
139
-
140
128
  async function ensurePrerenderDeps() {
141
129
  if (!_deserializeSegments) {
142
130
  const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
@@ -148,6 +136,7 @@ async function ensurePrerenderDeps() {
148
136
  ]);
149
137
  _deserializeSegments = codec.deserializeSegments;
150
138
  _restoreHandles = snapshot.restoreHandles;
139
+ _decodeHandles = snapshot.decodeHandles;
151
140
  _hashParams = paramHash.hashParams;
152
141
  _lazyGetRequestContext = reqCtx.getRequestContext;
153
142
  if (prerenderStoreInstance === undefined) {
@@ -161,69 +150,21 @@ async function ensurePrerenderDeps() {
161
150
  * Deserializes segments, replays handle data, yields segments with partial
162
151
  * navigation nullification, and resolves fresh loaders.
163
152
  */
164
- async function* yieldFromStore<TEnv>(
165
- entry: PrerenderEntry,
153
+ // Resolve loaders fresh on a cache hit (loaders are NEVER cached) and yield
154
+ // the loader segments, updating state.matchedIds and pushing the loader-resolve
155
+ // + cache-hit metrics. Shared verbatim by the prerender-store path
156
+ // (yieldFromStore) and the runtime cache-hit path (withCacheLookup). The router
157
+ // resolve fns are passed in (captured early by the callers) rather than re-read
158
+ // from getRouterContext() here, because that is ALS-backed and the callers run
159
+ // awaits before reaching this point (workerd can disrupt ALS mid-pipeline).
160
+ async function* resolveFreshLoadersAndYield<TEnv>(
166
161
  ctx: MatchContext<TEnv>,
167
162
  state: MatchPipelineState,
168
163
  pipelineStart: number,
169
- handleStoreRef?: HandleStore,
164
+ ms: MatchContext<TEnv>["metricsStore"],
165
+ resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
166
+ resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
170
167
  ): AsyncGenerator<ResolvedSegment> {
171
- const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
172
- getRouterContext<TEnv>();
173
-
174
- if (
175
- !_deserializeSegments ||
176
- !_restoreHandles ||
177
- !_hashParams ||
178
- !_lazyGetRequestContext
179
- ) {
180
- throw new Error("yieldFromStore called before ensurePrerenderDeps");
181
- }
182
-
183
- const segments = await _deserializeSegments(entry.segments);
184
-
185
- // Replay handle data (same as runtime cache hit path).
186
- // Prefer the eagerly-captured handleStoreRef to avoid ALS disruption in workerd.
187
- const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
188
- if (handleStore) {
189
- _restoreHandles(entry.handles, handleStore);
190
- }
191
-
192
- state.cacheHit = true;
193
- state.cacheSource = "prerender";
194
- state.cachedSegments = segments;
195
- state.cachedMatchedIds = segments.map((s) => s.id);
196
-
197
- // Set streaming flag (once) and resolve render barrier.
198
- const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
199
- const barrierReqCtx = reqCtx ?? _getRequestContext();
200
- if (barrierReqCtx) {
201
- if (barrierReqCtx._treeHasStreaming === undefined) {
202
- barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
203
- }
204
- barrierReqCtx._resolveRenderBarrier(segments);
205
- }
206
-
207
- // For partial navigation, nullify components the client already has
208
- // so parent layouts stay live (client keeps its existing versions).
209
- // When params changed (e.g., different guide slug), the segments have
210
- // different content, so we must NOT nullify.
211
- const paramsChanged =
212
- !ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
213
- for (const segment of segments) {
214
- if (
215
- !ctx.isFullMatch &&
216
- !paramsChanged &&
217
- ctx.clientSegmentSet.has(segment.id)
218
- ) {
219
- segment.component = null;
220
- segment.loading = undefined;
221
- }
222
- yield segment;
223
- }
224
-
225
- // Resolve loaders fresh (loaders are never pre-rendered/cached)
226
- const ms = ctx.metricsStore;
227
168
  const loaderStart = performance.now();
228
169
 
229
170
  if (ctx.isFullMatch) {
@@ -251,6 +192,8 @@ async function* yieldFromStore<TEnv>(
251
192
  ctx.url,
252
193
  ctx.routeKey,
253
194
  ctx.actionContext,
195
+ // Loaders are never cached in the segment cache, so segment staleness
196
+ // must not propagate; browser-sent staleness (ctx.stale) still must.
254
197
  ctx.stale || undefined,
255
198
  ),
256
199
  );
@@ -282,6 +225,83 @@ async function* yieldFromStore<TEnv>(
282
225
  }
283
226
  }
284
227
 
228
+ async function* yieldFromStore<TEnv>(
229
+ entry: PrerenderEntry,
230
+ ctx: MatchContext<TEnv>,
231
+ state: MatchPipelineState,
232
+ pipelineStart: number,
233
+ handleStoreRef?: HandleStore,
234
+ ): AsyncGenerator<ResolvedSegment> {
235
+ const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
236
+ getRouterContext<TEnv>();
237
+
238
+ if (
239
+ !_deserializeSegments ||
240
+ !_restoreHandles ||
241
+ !_decodeHandles ||
242
+ !_hashParams ||
243
+ !_lazyGetRequestContext
244
+ ) {
245
+ throw new Error("yieldFromStore called before ensurePrerenderDeps");
246
+ }
247
+
248
+ const segments = await _deserializeSegments(entry.segments);
249
+
250
+ // Replay handle data (same as runtime cache hit path). entry.handles is a
251
+ // Flight-encoded string ("" when none) — decode before restore so
252
+ // Promise/ReactNode handle values are revived, not the corrupted JSON form.
253
+ const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
254
+ if (handleStore && entry.handles) {
255
+ const handlesRecord = await _decodeHandles(entry.handles);
256
+ if (handlesRecord) {
257
+ _restoreHandles(handlesRecord, handleStore);
258
+ }
259
+ }
260
+
261
+ state.cacheHit = true;
262
+ state.cacheSource = "prerender";
263
+ state.cachedSegments = segments;
264
+ state.cachedMatchedIds = segments.map((s) => s.id);
265
+
266
+ // Set streaming flag (once) and resolve render barrier.
267
+ const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
268
+ const barrierReqCtx = reqCtx ?? _getRequestContext();
269
+ if (barrierReqCtx) {
270
+ if (barrierReqCtx._treeHasStreaming === undefined) {
271
+ barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
272
+ }
273
+ barrierReqCtx._resolveRenderBarrier(segments);
274
+ }
275
+
276
+ // For partial navigation, nullify components the client already has
277
+ // so parent layouts stay live (client keeps its existing versions).
278
+ // When params changed (e.g., different guide slug), the segments have
279
+ // different content, so we must NOT nullify.
280
+ const paramsChanged =
281
+ !ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
282
+ for (const segment of segments) {
283
+ if (
284
+ !ctx.isFullMatch &&
285
+ !paramsChanged &&
286
+ ctx.clientSegmentSet.has(segment.id)
287
+ ) {
288
+ segment.component = null;
289
+ segment.loading = undefined;
290
+ }
291
+ yield segment;
292
+ }
293
+
294
+ // Resolve loaders fresh (loaders are never pre-rendered/cached).
295
+ yield* resolveFreshLoadersAndYield(
296
+ ctx,
297
+ state,
298
+ pipelineStart,
299
+ ctx.metricsStore,
300
+ resolveLoadersOnly,
301
+ resolveLoadersOnlyWithRevalidation,
302
+ );
303
+ }
304
+
285
305
  /**
286
306
  * Look up a prerendered (build-time cached) entry for the current route and, on
287
307
  * a hit, yield its segments. Returns true when an entry was served (the caller
@@ -358,10 +378,6 @@ export function withCacheLookup<TEnv>(
358
378
  resolveLoadersOnly,
359
379
  } = getRouterContext<TEnv>();
360
380
 
361
- // Prerender lookup: check build-time cached data before runtime cache.
362
- // Prerender data is available regardless of runtime cache configuration.
363
- // Skip for HMR requests — the dev prerender endpoint reads from a stale
364
- // RouterRegistry snapshot; rendering fresh ensures edits are visible.
365
381
  const isHmr = !!ctx.request.headers.get("X-RSC-HMR");
366
382
  if (!ctx.isAction && !isHmr && ctx.matched.pr) {
367
383
  await ensurePrerenderDeps();
@@ -376,14 +392,6 @@ export function withCacheLookup<TEnv>(
376
392
  }
377
393
  }
378
394
 
379
- // Dev-mode static handler interception for non-Node.js runtimes.
380
- // __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
381
- // lacks a Node.js module runner (e.g. workerd, Deno workers). In those
382
- // runtimes, handlers that depend on Node APIs like node:fs can't run
383
- // in-process. We redirect them to the /__rsc_prerender endpoint which
384
- // resolves segments in a Node.js temp server, same as prerender routes.
385
- // In Node.js dev mode this variable is undefined -- handlers run
386
- // in-process where Node APIs work, so no interception is needed.
387
395
  if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
388
396
  const hasStatic = ctx.entries.some(
389
397
  (e) =>
@@ -406,9 +414,7 @@ export function withCacheLookup<TEnv>(
406
414
  }
407
415
  }
408
416
 
409
- // Skip cache during actions
410
417
  if (ctx.isAction || !ctx.cacheScope?.enabled) {
411
- // Cache miss - pass through to segment resolution
412
418
  yield* source;
413
419
  if (ms) {
414
420
  ms.metrics.push({
@@ -420,7 +426,6 @@ export function withCacheLookup<TEnv>(
420
426
  return;
421
427
  }
422
428
 
423
- // Lookup cache
424
429
  const cacheResult = await ctx.cacheScope.lookupRoute(
425
430
  ctx.pathname,
426
431
  ctx.matched.params,
@@ -428,7 +433,6 @@ export function withCacheLookup<TEnv>(
428
433
  );
429
434
 
430
435
  if (!cacheResult) {
431
- // Cache miss - pass through to segment resolution
432
436
  yield* source;
433
437
  if (ms) {
434
438
  ms.metrics.push({
@@ -440,16 +444,12 @@ export function withCacheLookup<TEnv>(
440
444
  return;
441
445
  }
442
446
 
443
- // Cache HIT
444
447
  state.cacheHit = true;
445
448
  state.cacheSource = "runtime";
446
449
  state.shouldRevalidate = cacheResult.shouldRevalidate;
447
450
  state.cachedSegments = cacheResult.segments;
448
451
  state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
449
452
 
450
- // Apply revalidation to cached segments.
451
- // For full matches or empty client segment sets, this map is unnecessary:
452
- // we never run segment-level revalidation and can stream segments directly.
453
453
  const canCheckSegmentRevalidation =
454
454
  !ctx.isFullMatch &&
455
455
  ctx.clientSegmentSet.size > 0 &&
@@ -459,7 +459,6 @@ export function withCacheLookup<TEnv>(
459
459
  : undefined;
460
460
 
461
461
  for (const segment of cacheResult.segments) {
462
- // Skip segments client doesn't have - they need their component
463
462
  if (!ctx.clientSegmentSet.has(segment.id)) {
464
463
  if (isTraceActive()) {
465
464
  pushRevalidationTraceEntry({
@@ -476,19 +475,13 @@ export function withCacheLookup<TEnv>(
476
475
  continue;
477
476
  }
478
477
 
479
- // Skip intercept segments - they're handled separately
480
478
  if (segment.namespace?.startsWith("intercept:")) {
481
479
  yield segment;
482
480
  continue;
483
481
  }
484
482
 
485
- // Look up revalidation rules for this segment
486
483
  const entryInfo = entryRevalidateMap?.get(segment.id);
487
484
 
488
- // Even without explicit revalidation rules, route segments and their
489
- // children must re-render when params or search params change — the
490
- // handler reads ctx.params/ctx.searchParams so different values produce
491
- // different content. Matches evaluateRevalidation's default logic.
492
485
  const searchChanged = ctx.prevUrl.search !== ctx.url.search;
493
486
  const routeParamsChanged = !paramsEqual(
494
487
  ctx.matched.params,
@@ -502,7 +495,6 @@ export function withCacheLookup<TEnv>(
502
495
 
503
496
  if (!entryInfo || entryInfo.revalidate.length === 0) {
504
497
  if (shouldDefaultRevalidate) {
505
- // Params or search params changed — must re-render even without custom rules
506
498
  if (isTraceActive()) {
507
499
  pushRevalidationTraceEntry({
508
500
  segmentId: segment.id,
@@ -519,7 +511,6 @@ export function withCacheLookup<TEnv>(
519
511
  yield segment;
520
512
  continue;
521
513
  }
522
- // No revalidation rules, use default behavior (skip if client has)
523
514
  if (isTraceActive()) {
524
515
  pushRevalidationTraceEntry({
525
516
  segmentId: segment.id,
@@ -537,7 +528,6 @@ export function withCacheLookup<TEnv>(
537
528
  continue;
538
529
  }
539
530
 
540
- // Evaluate revalidation rules
541
531
  const shouldRevalidate = await evaluateRevalidation({
542
532
  segment,
543
533
  prevParams: ctx.prevParams,
@@ -556,22 +546,16 @@ export function withCacheLookup<TEnv>(
556
546
  traceSource: "cache-hit",
557
547
  });
558
548
 
559
- const routerCtx = getRouterContext<TEnv>();
560
- if (routerCtx.telemetry) {
561
- const tSink = resolveSink(routerCtx.telemetry);
562
- safeEmit(tSink, {
563
- type: "revalidation.decision",
564
- timestamp: performance.now(),
565
- requestId: routerCtx.requestId,
566
- segmentId: segment.id,
567
- pathname: ctx.pathname,
568
- routeKey: ctx.routeKey,
569
- shouldRevalidate,
570
- });
571
- }
549
+ observeEvent({
550
+ type: "revalidation.decision",
551
+ timestamp: performance.now(),
552
+ segmentId: segment.id,
553
+ pathname: ctx.pathname,
554
+ routeKey: ctx.routeKey,
555
+ shouldRevalidate,
556
+ });
572
557
 
573
558
  if (!shouldRevalidate) {
574
- // Client has it, no revalidation needed
575
559
  segment.component = null;
576
560
  segment.loading = undefined;
577
561
  }
@@ -579,7 +563,6 @@ export function withCacheLookup<TEnv>(
579
563
  yield segment;
580
564
  }
581
565
 
582
- // Set streaming flag (once) and resolve render barrier.
583
566
  const barrierReqCtx = _getRequestContext();
584
567
  if (barrierReqCtx) {
585
568
  if (barrierReqCtx._treeHasStreaming === undefined) {
@@ -588,77 +571,15 @@ export function withCacheLookup<TEnv>(
588
571
  barrierReqCtx._resolveRenderBarrier(cacheResult.segments);
589
572
  }
590
573
 
591
- // Resolve loaders fresh (loaders are NOT cached by default)
592
- // This ensures fresh data even on cache hit
593
- const Store = ctx.Store;
594
- const loaderStart = performance.now();
595
-
596
- if (ctx.isFullMatch) {
597
- // Full match (document request) - simple loader resolution without revalidation
598
- if (resolveLoadersOnly) {
599
- const loaderSegments = await Store.run(() =>
600
- resolveLoadersOnly(ctx.entries, ctx.handlerContext),
601
- );
602
-
603
- // Update state - full match doesn't track matchedIds separately
604
- state.matchedIds = state.cachedMatchedIds!;
605
-
606
- // Yield fresh loader segments
607
- for (const segment of loaderSegments) {
608
- yield segment;
609
- }
610
- } else {
611
- state.matchedIds = state.cachedMatchedIds!;
612
- }
613
- } else {
614
- // Partial match (navigation) - loader resolution with revalidation
615
- if (resolveLoadersOnlyWithRevalidation) {
616
- const loaderResult = await Store.run(() =>
617
- resolveLoadersOnlyWithRevalidation(
618
- ctx.entries,
619
- ctx.handlerContext,
620
- ctx.clientSegmentSet,
621
- ctx.prevParams,
622
- ctx.request,
623
- ctx.prevUrl,
624
- ctx.url,
625
- ctx.routeKey,
626
- ctx.actionContext,
627
- // Loaders are never cached in the segment cache, so segment
628
- // staleness (cacheResult.shouldRevalidate) must not propagate.
629
- // But browser-sent staleness (ctx.stale) — indicating an action
630
- // happened in this or another tab — must still reach loaders.
631
- ctx.stale || undefined,
632
- ),
633
- );
634
-
635
- // Update state with fresh loader matchedIds
636
- state.matchedIds = [
637
- ...state.cachedMatchedIds!,
638
- ...loaderResult.matchedIds,
639
- ];
640
-
641
- // Yield fresh loader segments
642
- for (const segment of loaderResult.segments) {
643
- yield segment;
644
- }
645
- } else {
646
- state.matchedIds = state.cachedMatchedIds!;
647
- }
648
- }
649
- if (ms) {
650
- const loaderEnd = performance.now();
651
- ms.metrics.push({
652
- label: "pipeline:loader-resolve",
653
- duration: loaderEnd - loaderStart,
654
- startTime: loaderStart - ms.requestStart,
655
- depth: 1,
656
- });
657
- ms.metrics.push({
658
- label: "pipeline:cache-hit",
659
- duration: loaderEnd - pipelineStart,
660
- startTime: pipelineStart - ms.requestStart,
661
- });
662
- }
574
+ // Resolve loaders fresh (loaders are never cached). Shared with the
575
+ // prerender-store path via resolveFreshLoadersAndYield.
576
+ yield* resolveFreshLoadersAndYield(
577
+ ctx,
578
+ state,
579
+ pipelineStart,
580
+ ms,
581
+ resolveLoadersOnly,
582
+ resolveLoadersOnlyWithRevalidation,
583
+ );
663
584
  };
664
585
  }
@@ -101,7 +101,10 @@
101
101
  * - Non-GET request (only GET requests are cacheable)
102
102
  */
103
103
  import type { ResolvedSegment } from "../../types.js";
104
- import { getRequestContext } from "../../server/request-context.js";
104
+ import {
105
+ getRequestContext,
106
+ runWithRequestContext,
107
+ } from "../../server/request-context.js";
105
108
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
106
109
  import { getRouterContext } from "../router-context.js";
107
110
  import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
@@ -123,21 +126,14 @@ export function withCacheStore<TEnv>(
123
126
  ): AsyncGenerator<ResolvedSegment> {
124
127
  const ms = ctx.metricsStore;
125
128
 
126
- // Collect all segments while passing them through
127
129
  const allSegments: ResolvedSegment[] = [];
128
130
  for await (const segment of source) {
129
131
  allSegments.push(segment);
130
132
  yield segment;
131
133
  }
132
134
 
133
- // Measure own work only (after source iteration completes)
134
135
  const ownStart = performance.now();
135
136
 
136
- // Skip caching if:
137
- // 1. Cache miss but cache scope is disabled
138
- // 2. This is an action (actions don't cache)
139
- // 3. Cache was already hit (no need to re-cache)
140
- // 4. Non-GET request (only cache GET requests)
141
137
  if (
142
138
  !ctx.cacheScope?.enabled ||
143
139
  ctx.isAction ||
@@ -162,13 +158,17 @@ export function withCacheStore<TEnv>(
162
158
  createHandleStore,
163
159
  } = getRouterContext<TEnv>();
164
160
 
165
- // Combine main segments with intercept segments
166
- const allSegmentsToCache = [...allSegments, ...state.interceptSegments];
161
+ // On a fresh intercept miss the intercept slot segments flow through
162
+ // `source` into allSegments AND are also recorded on state.interceptSegments.
163
+ // Dedup by id so each segment is cached once — cacheRoute serializes the
164
+ // array verbatim (no id-dedup), so an un-deduped append doubles the intercept
165
+ // segment in the cached entry, re-rendering the slot twice on the next hit.
166
+ const seenSegmentIds = new Set(allSegments.map((s) => s.id));
167
+ const allSegmentsToCache = [
168
+ ...allSegments,
169
+ ...state.interceptSegments.filter((s) => !seenSegmentIds.has(s.id)),
170
+ ];
167
171
 
168
- // Check if any non-loader segments have null components from revalidation
169
- // skip (client already had them). Segments where the handler intentionally
170
- // returned null are not revalidation skips — re-rendering them will still
171
- // produce null, so proactive caching would be wasted work.
172
172
  const hasNullComponents = allSegmentsToCache.some(
173
173
  (s) =>
174
174
  s.component === null &&
@@ -180,14 +180,23 @@ export function withCacheStore<TEnv>(
180
180
  if (!requestCtx) return;
181
181
 
182
182
  const cacheScope = ctx.cacheScope;
183
+
184
+ // Record the route's segment-DSL cache tags into the request tag union NOW,
185
+ // synchronously in the pipeline. The actual store write (cacheRoute) runs in
186
+ // requestCtx.waitUntil() below — and the proactive path re-resolves the whole
187
+ // tree first — so cacheRoute's own tag recording races the document cache's
188
+ // post-body-drain snapshot of _requestTags. On a first-write miss the document
189
+ // tag union could miss these tags and revalidateTag()/updateTag() would not
190
+ // invalidate the cached document. Recording here (before the snapshot) closes
191
+ // the window for both the direct and proactive write paths; the duplicate
192
+ // record inside cacheRoute is idempotent.
193
+ cacheScope.recordTags(requestCtx);
194
+
183
195
  const reqId = INTERNAL_RANGO_DEBUG
184
196
  ? getOrCreateRequestId(ctx.request)
185
197
  : undefined;
186
198
 
187
- // Register onResponse callback to skip caching for non-200 responses
188
- // Note: error/notFound status codes are set elsewhere (not caching-specific)
189
199
  requestCtx.onResponse((response) => {
190
- // Only cache successful responses
191
200
  if (response.status !== 200) {
192
201
  debugLog("cacheStore", "skipping cache for non-200 response", {
193
202
  status: response.status,
@@ -197,10 +206,7 @@ export function withCacheStore<TEnv>(
197
206
  }
198
207
 
199
208
  if (hasNullComponents) {
200
- // Proactive caching: render all segments fresh in background
201
- // This ensures cache has complete components for future requests
202
209
  requestCtx.waitUntil(async () => {
203
- // Prevent background metrics from polluting foreground timeline.
204
210
  const savedMetrics = ctx.Store.metrics;
205
211
  ctx.Store.metrics = undefined;
206
212
 
@@ -208,15 +214,9 @@ export function withCacheStore<TEnv>(
208
214
  debugLog("cacheStore", "proactive caching started", {
209
215
  pathname: ctx.pathname,
210
216
  });
211
- // Swap to a fresh HandleStore so handle.push() calls from
212
- // proactive resolution are captured (not silenced). The original
213
- // store's stream is already sent by waitUntil time.
214
- // cacheRoute reads from requestCtx._handleStore, so this ensures
215
- // complete handle data (e.g. breadcrumbs) is cached.
216
217
  const originalHandleStore = requestCtx._handleStore;
217
218
  requestCtx._handleStore = createHandleStore();
218
219
  try {
219
- // Create fresh context for proactive caching
220
220
  const proactiveHandlerContext = createHandlerContext(
221
221
  ctx.matched.params,
222
222
  ctx.request,
@@ -231,36 +231,49 @@ export function withCacheStore<TEnv>(
231
231
  );
232
232
  const proactiveLoaderPromises = new Map<string, Promise<any>>();
233
233
 
234
- // Use normal loader access so handle data is captured
235
234
  setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
236
235
 
237
- // Re-resolve ALL segments without revalidation.
238
- // Skip DSL loaders — they are never cached (cacheRoute filters them)
239
- // and are always resolved fresh on each request.
240
236
  const Store = ctx.Store;
241
- const freshSegments = await Store.run(() =>
242
- resolveAllSegments(
243
- ctx.entries,
244
- ctx.routeKey,
245
- ctx.matched.params,
246
- proactiveHandlerContext,
247
- proactiveLoaderPromises,
248
- { skipLoaders: true },
237
+ // Re-establish the request-context ALS around the re-render. Store
238
+ // is a different ALS (DSL build context); on workerd a waitUntil
239
+ // task runs detached from the request's I/O context, so a handler/
240
+ // component that reads the ambient getRequestContext() during this
241
+ // background re-render would otherwise throw "called outside of a
242
+ // request context".
243
+ const freshSegments = await runWithRequestContext(requestCtx, () =>
244
+ Store.run(() =>
245
+ resolveAllSegments(
246
+ ctx.entries,
247
+ ctx.routeKey,
248
+ ctx.matched.params,
249
+ proactiveHandlerContext,
250
+ proactiveLoaderPromises,
251
+ { skipLoaders: true },
252
+ ),
249
253
  ),
250
254
  );
251
255
 
252
- // Also resolve intercept segments fresh if applicable
253
256
  let freshInterceptSegments: ResolvedSegment[] = [];
254
257
  if (ctx.interceptResult) {
255
- freshInterceptSegments = await Store.run(() =>
256
- resolveInterceptEntry(
257
- ctx.interceptResult!.intercept,
258
- ctx.interceptResult!.entry,
259
- ctx.matched.params,
260
- proactiveHandlerContext,
261
- true, // belongsToRoute
262
- // No revalidationContext = render fresh
263
- ),
258
+ freshInterceptSegments = await runWithRequestContext(
259
+ requestCtx,
260
+ () =>
261
+ Store.run(() =>
262
+ resolveInterceptEntry(
263
+ ctx.interceptResult!.intercept,
264
+ ctx.interceptResult!.entry,
265
+ ctx.matched.params,
266
+ proactiveHandlerContext,
267
+ true, // belongsToRoute
268
+ // No revalidationContext = render fresh
269
+ undefined,
270
+ // Skip intercept middleware: the foreground already ran it
271
+ // before the response was sent. Re-running here (post-
272
+ // response, background) would fire side effects twice and a
273
+ // short-circuit Response would silently abort this write.
274
+ { skipMiddleware: true },
275
+ ),
276
+ ),
264
277
  );
265
278
  }
266
279
 
@@ -301,8 +314,6 @@ export function withCacheStore<TEnv>(
301
314
  }
302
315
  });
303
316
  } else {
304
- // All segments have components - cache directly
305
- // Schedule caching in waitUntil since cacheRoute is now async (key resolution)
306
317
  if (INTERNAL_RANGO_DEBUG) {
307
318
  console.log(
308
319
  `[RSC CacheStore][req:${reqId}] Direct cache path: scheduling cacheRoute for ${ctx.pathname} (${allSegmentsToCache.length} segments, hasNullComponents=${hasNullComponents})`,