@rangojs/router 0.20.0 → 0.22.0

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 (331) hide show
  1. package/dist/bin/rango.js +39 -15
  2. package/dist/types/browser/event-controller.d.ts +49 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-store.d.ts +9 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  6. package/dist/types/browser/partial-update.d.ts +2 -1
  7. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  8. package/dist/types/browser/rango-state.d.ts +3 -1
  9. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  10. package/dist/types/browser/react/context.d.ts +25 -0
  11. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  12. package/dist/types/browser/react/location-state.d.ts +7 -2
  13. package/dist/types/browser/rsc-router.d.ts +13 -2
  14. package/dist/types/browser/types.d.ts +10 -0
  15. package/dist/types/build/generate-manifest.d.ts +2 -0
  16. package/dist/types/cache/background-task.d.ts +22 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  18. package/dist/types/cache/cache-policy.d.ts +17 -0
  19. package/dist/types/cache/cache-scope.d.ts +5 -6
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  22. package/dist/types/cache/document-cache.d.ts +8 -5
  23. package/dist/types/cache/handle-snapshot.d.ts +59 -50
  24. package/dist/types/cache/index.d.ts +3 -1
  25. package/dist/types/cache/invalidation-order.d.ts +14 -0
  26. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  27. package/dist/types/cache/shell-snapshot.d.ts +29 -4
  28. package/dist/types/cache/store-scope.d.ts +22 -0
  29. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  30. package/dist/types/cache/types.d.ts +84 -24
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
  32. package/dist/types/client-urls/navigation.d.ts +7 -1
  33. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  34. package/dist/types/errors.d.ts +6 -0
  35. package/dist/types/handles/deferred-resolution.d.ts +18 -1
  36. package/dist/types/internal-suspense-audit.d.ts +1 -0
  37. package/dist/types/loader-store.d.ts +4 -8
  38. package/dist/types/outlet-provider.d.ts +2 -1
  39. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  40. package/dist/types/prerender/cloudflare.d.ts +49 -0
  41. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  42. package/dist/types/prerender/index.d.ts +12 -0
  43. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  44. package/dist/types/prerender/on-demand.d.ts +272 -0
  45. package/dist/types/prerender/producer-guard.d.ts +33 -0
  46. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  47. package/dist/types/prerender/warm-request.d.ts +65 -0
  48. package/dist/types/prerender/warm.d.ts +65 -0
  49. package/dist/types/prerender/writable-store.d.ts +170 -0
  50. package/dist/types/prerender.d.ts +27 -0
  51. package/dist/types/reverse.d.ts +1 -1
  52. package/dist/types/route-content-wrapper.d.ts +3 -1
  53. package/dist/types/router/error-handling.d.ts +2 -0
  54. package/dist/types/router/handler-context.d.ts +1 -1
  55. package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
  56. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  57. package/dist/types/router/pattern-matching.d.ts +2 -0
  58. package/dist/types/router/prefetch-default.d.ts +4 -4
  59. package/dist/types/router/prerender-match.d.ts +23 -1
  60. package/dist/types/router/route-trie-builder.d.ts +8 -1
  61. package/dist/types/router/router-context.d.ts +1 -0
  62. package/dist/types/router/router-interfaces.d.ts +32 -1
  63. package/dist/types/router/router-options.d.ts +39 -5
  64. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  65. package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  67. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  68. package/dist/types/router/trie-matching.d.ts +2 -0
  69. package/dist/types/router-versions.d.ts +39 -0
  70. package/dist/types/rsc/handler-context.d.ts +5 -0
  71. package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
  72. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  73. package/dist/types/rsc/shell-capture.d.ts +14 -1
  74. package/dist/types/rsc/shell-serve.d.ts +2 -2
  75. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  76. package/dist/types/rsc/types.d.ts +12 -6
  77. package/dist/types/segment-boundary-content.d.ts +17 -0
  78. package/dist/types/segment-loader-promise.d.ts +2 -6
  79. package/dist/types/server/build-version-table.d.ts +53 -0
  80. package/dist/types/server/context.d.ts +12 -4
  81. package/dist/types/server/handle-store.d.ts +89 -46
  82. package/dist/types/server/request-context.d.ts +84 -9
  83. package/dist/types/suspense-audit-react.d.ts +26 -0
  84. package/dist/types/suspense-audit.d.ts +157 -0
  85. package/dist/types/testing/action-form.d.ts +34 -0
  86. package/dist/types/testing/build-versions.d.ts +32 -0
  87. package/dist/types/testing/dom.entry.d.ts +1 -1
  88. package/dist/types/testing/flight.entry.d.ts +3 -1
  89. package/dist/types/testing/index.d.ts +6 -0
  90. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  91. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  92. package/dist/types/testing/location-state-key.d.ts +5 -1
  93. package/dist/types/testing/render-route.d.ts +77 -6
  94. package/dist/types/testing/serve-shell-request.d.ts +55 -4
  95. package/dist/types/testing/shell-status.d.ts +14 -6
  96. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  97. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  98. package/dist/types/thenable-status.d.ts +6 -0
  99. package/dist/types/types/handler-context.d.ts +15 -5
  100. package/dist/types/types/route-entry.d.ts +5 -0
  101. package/dist/types/urls/path-helper.d.ts +18 -0
  102. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  103. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  104. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  105. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  106. package/dist/types/vite/discovery/state.d.ts +25 -0
  107. package/dist/types/vite/encryption-key.d.ts +18 -0
  108. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  109. package/dist/types/vite/plugin-types.d.ts +27 -0
  110. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  111. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  112. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  113. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  114. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  115. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  116. package/dist/types/vite/router-discovery.d.ts +6 -0
  117. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  118. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  119. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  120. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  121. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  122. package/dist/vite/index.js +1707 -931
  123. package/package.json +12 -3
  124. package/skills/cache-guide/SKILL.md +102 -37
  125. package/skills/caching/SKILL.md +56 -9
  126. package/skills/catalog.json +1 -1
  127. package/skills/client-urls/SKILL.md +16 -0
  128. package/skills/cloudflare/SKILL.md +133 -1
  129. package/skills/comparison/references/framework-comparison.md +17 -13
  130. package/skills/deployment-caching/SKILL.md +33 -12
  131. package/skills/document-cache/SKILL.md +16 -14
  132. package/skills/hooks/SKILL.md +25 -0
  133. package/skills/hooks/handle-and-actions.md +5 -0
  134. package/skills/hooks/navigation.md +38 -0
  135. package/skills/hooks/state.md +213 -11
  136. package/skills/hooks/urls.md +40 -1
  137. package/skills/host-router/SKILL.md +31 -0
  138. package/skills/intercept/SKILL.md +6 -0
  139. package/skills/loader/SKILL.md +25 -5
  140. package/skills/middleware/SKILL.md +2 -0
  141. package/skills/migrate-nextjs/SKILL.md +82 -9
  142. package/skills/ppr/SKILL.md +150 -48
  143. package/skills/prerender/SKILL.md +651 -10
  144. package/skills/rango/SKILL.md +9 -7
  145. package/skills/response-routes/SKILL.md +5 -5
  146. package/skills/route/SKILL.md +29 -0
  147. package/skills/router-setup/SKILL.md +15 -1
  148. package/skills/testing/SKILL.md +32 -27
  149. package/skills/testing/cache-prerender.md +18 -17
  150. package/skills/testing/client-components.md +193 -8
  151. package/skills/testing/handles.md +2 -0
  152. package/skills/testing/loader.md +1 -1
  153. package/skills/testing/middleware.md +1 -1
  154. package/skills/testing/render-handler.md +18 -18
  155. package/skills/testing/serve-shell-request.md +167 -22
  156. package/skills/testing/server-actions.md +17 -17
  157. package/skills/testing/server-tree.md +1 -1
  158. package/skills/typesafety/generated-files-and-cli.md +1 -1
  159. package/skills/use-cache/SKILL.md +25 -10
  160. package/skills/vercel/SKILL.md +5 -3
  161. package/skills/view-transitions/SKILL.md +9 -4
  162. package/src/bin/rango.ts +1 -1
  163. package/src/browser/event-controller.ts +119 -2
  164. package/src/browser/history-state.ts +39 -17
  165. package/src/browser/link-interceptor.ts +2 -2
  166. package/src/browser/navigation-bridge.ts +104 -41
  167. package/src/browser/navigation-store.ts +26 -0
  168. package/src/browser/navigation-transaction.ts +24 -12
  169. package/src/browser/network-error-handler.ts +2 -0
  170. package/src/browser/partial-update.ts +58 -14
  171. package/src/browser/prefetch/default-strategy.ts +22 -42
  172. package/src/browser/rango-state.ts +3 -1
  173. package/src/browser/react/Link.tsx +37 -43
  174. package/src/browser/react/NavigationProvider.tsx +85 -1
  175. package/src/browser/react/context.ts +30 -0
  176. package/src/browser/react/location-state-shared.ts +200 -34
  177. package/src/browser/react/location-state.ts +28 -77
  178. package/src/browser/react/use-action.ts +4 -1
  179. package/src/browser/react/use-handle.ts +3 -0
  180. package/src/browser/react/use-link-status.ts +4 -3
  181. package/src/browser/react/use-navigation.ts +4 -1
  182. package/src/browser/react/use-params.ts +4 -1
  183. package/src/browser/react/use-pathname.ts +4 -1
  184. package/src/browser/react/use-search-params.ts +5 -3
  185. package/src/browser/react/use-segments.ts +36 -54
  186. package/src/browser/rsc-router.tsx +93 -31
  187. package/src/browser/segment-reconciler.ts +3 -3
  188. package/src/browser/server-action-bridge.ts +43 -7
  189. package/src/browser/types.ts +11 -0
  190. package/src/build/generate-manifest.ts +17 -0
  191. package/src/build/merge-full-manifests.ts +5 -0
  192. package/src/build/route-trie.ts +1 -0
  193. package/src/cache/background-task.ts +61 -0
  194. package/src/cache/cache-key-utils.ts +91 -12
  195. package/src/cache/cache-policy.ts +11 -2
  196. package/src/cache/cache-runtime.ts +23 -23
  197. package/src/cache/cache-scope.ts +48 -36
  198. package/src/cache/cf/cf-cache-store.ts +91 -66
  199. package/src/cache/cf/cf-cache-types.ts +12 -4
  200. package/src/cache/document-cache.ts +35 -14
  201. package/src/cache/handle-snapshot.ts +118 -86
  202. package/src/cache/index.ts +5 -0
  203. package/src/cache/invalidation-order.ts +53 -0
  204. package/src/cache/memory-segment-store.ts +52 -1
  205. package/src/cache/shell-snapshot.ts +52 -5
  206. package/src/cache/store-scope.ts +36 -0
  207. package/src/cache/tag-invalidation.ts +7 -5
  208. package/src/cache/types.ts +90 -24
  209. package/src/cache/vercel/vercel-cache-store.ts +68 -26
  210. package/src/client-urls/client-root.tsx +1 -0
  211. package/src/client-urls/navigation.ts +10 -0
  212. package/src/client-urls/optimistic-location.ts +10 -4
  213. package/src/client.tsx +40 -3
  214. package/src/errors.ts +13 -6
  215. package/src/handles/deferred-resolution.ts +36 -0
  216. package/src/internal-suspense-audit.ts +12 -0
  217. package/src/loader-store.ts +4 -9
  218. package/src/outlet-provider.tsx +25 -1
  219. package/src/prerender/build-shell-capture.ts +11 -9
  220. package/src/prerender/cloudflare.ts +130 -0
  221. package/src/prerender/create-prerender-trigger.ts +780 -0
  222. package/src/prerender/index.ts +39 -0
  223. package/src/prerender/memory-prerender-store.ts +115 -0
  224. package/src/prerender/on-demand.ts +339 -0
  225. package/src/prerender/producer-guard.ts +66 -0
  226. package/src/prerender/shell-manifest-key.ts +12 -17
  227. package/src/prerender/warm-request.ts +154 -0
  228. package/src/prerender/warm.ts +193 -0
  229. package/src/prerender/writable-store.ts +278 -0
  230. package/src/prerender.ts +29 -0
  231. package/src/reverse.ts +1 -1
  232. package/src/route-content-wrapper.tsx +107 -13
  233. package/src/router/error-handling.ts +12 -12
  234. package/src/router/find-match.ts +1 -0
  235. package/src/router/handler-context.ts +2 -0
  236. package/src/router/intercept-resolution.ts +15 -8
  237. package/src/router/loader-resolution.ts +2 -10
  238. package/src/router/manifest.ts +2 -0
  239. package/src/router/match-middleware/background-revalidation.ts +2 -0
  240. package/src/router/match-middleware/cache-lookup.ts +239 -54
  241. package/src/router/navigation-snapshot.ts +25 -6
  242. package/src/router/pattern-matching.ts +14 -0
  243. package/src/router/prefetch-default.ts +4 -4
  244. package/src/router/prerender-match.ts +460 -352
  245. package/src/router/request-classification.ts +15 -12
  246. package/src/router/route-trie-builder.ts +9 -0
  247. package/src/router/router-context.ts +1 -0
  248. package/src/router/router-interfaces.ts +35 -1
  249. package/src/router/router-options.ts +45 -5
  250. package/src/router/segment-resolution/fresh.ts +20 -19
  251. package/src/router/segment-resolution/loader-cache.ts +110 -77
  252. package/src/router/segment-resolution/revalidation.ts +34 -24
  253. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  254. package/src/router/trie-matching.ts +3 -0
  255. package/src/router-versions.ts +60 -0
  256. package/src/router.ts +107 -1
  257. package/src/rsc/full-payload.ts +6 -2
  258. package/src/rsc/handler-context.ts +5 -0
  259. package/src/rsc/handler.ts +64 -7
  260. package/src/rsc/progressive-enhancement.ts +5 -1
  261. package/src/rsc/response-cache-serve.ts +10 -4
  262. package/src/rsc/rsc-rendering.ts +65 -12
  263. package/src/rsc/shell-build-manifest.ts +17 -12
  264. package/src/rsc/shell-capture-constants.ts +39 -7
  265. package/src/rsc/shell-capture.ts +144 -45
  266. package/src/rsc/shell-serve.ts +12 -6
  267. package/src/rsc/ssr-module-loader.ts +26 -0
  268. package/src/rsc/types.ts +12 -6
  269. package/src/segment-boundary-content.ts +25 -0
  270. package/src/segment-loader-promise.ts +28 -38
  271. package/src/segment-system.tsx +117 -57
  272. package/src/server/build-version-table.ts +130 -0
  273. package/src/server/context.ts +40 -17
  274. package/src/server/cookie-store.ts +5 -0
  275. package/src/server/handle-store.ts +357 -127
  276. package/src/server/request-context.ts +149 -12
  277. package/src/ssr/ssr-root.tsx +32 -13
  278. package/src/suspense-audit-react.tsx +98 -0
  279. package/src/suspense-audit.ts +1030 -0
  280. package/src/testing/action-form.ts +66 -0
  281. package/src/testing/build-versions.ts +64 -0
  282. package/src/testing/dispatch.ts +6 -0
  283. package/src/testing/dom.entry.ts +1 -0
  284. package/src/testing/flight-runtime.d.ts +27 -0
  285. package/src/testing/flight.entry.ts +4 -0
  286. package/src/testing/index.ts +22 -0
  287. package/src/testing/internal/context.ts +2 -1
  288. package/src/testing/internal/seed-vars.ts +14 -2
  289. package/src/testing/internal/server-modules.ts +32 -0
  290. package/src/testing/location-state-key.ts +18 -8
  291. package/src/testing/render-handler.ts +2 -1
  292. package/src/testing/render-route.tsx +293 -46
  293. package/src/testing/serve-shell-request.ts +262 -37
  294. package/src/testing/shell-status.ts +17 -6
  295. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  296. package/src/testing/vitest-stubs/version.ts +4 -1
  297. package/src/thenable-status.ts +10 -0
  298. package/src/types/handler-context.ts +15 -5
  299. package/src/types/route-entry.ts +6 -0
  300. package/src/urls/path-helper.ts +69 -9
  301. package/src/use-loader.tsx +57 -30
  302. package/src/vite/discovery/build-versions.ts +1281 -0
  303. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  304. package/src/vite/discovery/discover-routers.ts +15 -0
  305. package/src/vite/discovery/installed-packages.ts +159 -0
  306. package/src/vite/discovery/prerender-collection.ts +38 -1
  307. package/src/vite/discovery/router-versions-phase.ts +336 -0
  308. package/src/vite/discovery/shell-prerender-phase.ts +33 -43
  309. package/src/vite/discovery/state.ts +36 -1
  310. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  311. package/src/vite/encryption-key.ts +101 -9
  312. package/src/vite/inject-client-debug.ts +42 -20
  313. package/src/vite/plugin-types.ts +28 -0
  314. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  315. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  316. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  317. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  318. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  319. package/src/vite/plugins/version-plugin.ts +19 -24
  320. package/src/vite/plugins/version.d.ts +17 -2
  321. package/src/vite/plugins/virtual-entries.ts +39 -6
  322. package/src/vite/rango.ts +28 -3
  323. package/src/vite/router-discovery.ts +96 -13
  324. package/src/vite/utils/bundle-analysis.ts +39 -7
  325. package/src/vite/utils/client-chunks.ts +5 -1
  326. package/src/vite/utils/compare-strings.ts +9 -0
  327. package/src/vite/utils/package-resolution.ts +9 -0
  328. package/src/vite/utils/prerender-utils.ts +4 -1
  329. package/src/vite/utils/shared-utils.ts +1 -1
  330. package/dist/types/segment-content-promise.d.ts +0 -13
  331. package/src/segment-content-promise.ts +0 -67
@@ -46,7 +46,11 @@ import { withDefer } from "../defer.js";
46
46
  import { type MetricsStore } from "./context.js";
47
47
  import { observePhase, PHASES } from "../router/instrument.js";
48
48
  import { getFetchableLoader } from "./fetchable-loader-store.js";
49
- import type { SegmentCacheStore } from "../cache/types.js";
49
+ import type {
50
+ SegmentCacheStore,
51
+ ShellSnapshotHandlesValue,
52
+ } from "../cache/types.js";
53
+ import type { RouterVersions } from "../router-versions.js";
50
54
  import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
51
55
  import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
52
56
  import type { TransitionWhenRecord } from "../router/transition-when.js";
@@ -288,6 +292,30 @@ export interface RequestContext<
288
292
  /** @internal Cache store for segment caching (optional, used by CacheScope) */
289
293
  _cacheStore?: SegmentCacheStore;
290
294
 
295
+ /**
296
+ * @internal Env-scoped writable prerender store (the durable overlay) plus the
297
+ * deployment identity for build-scoped keys, resolved per request from the
298
+ * `prerender` router option. Read by the serve-path overlay lookup before the
299
+ * bundled build manifest. Absent when no prerender store is configured.
300
+ */
301
+ // ResolvedPrerender<any> (not <TEnv>): onRevalidate's contravariant `env` would
302
+ // otherwise make RequestContext invariant in TEnv, breaking existing
303
+ // RequestContext<{}> -> RequestContext<unknown> assignments.
304
+ _prerender?: import("../prerender/on-demand.js").ResolvedPrerender<any>;
305
+
306
+ /**
307
+ * @internal Set only on the requestless on-demand producer's context. When
308
+ * present, the standalone request-scoped READS — `cookies()`, `headers()`,
309
+ * `invalidateClientCache()`, `keepClientCache()` — throw a
310
+ * PrerenderPersonalizationError so a personalized render is skipped rather than
311
+ * baked into a shared payload. Response MUTATIONS on the producer context
312
+ * (`ctx.setStatus`/`header`/`setCookie`) stay silent no-op stubs, matching
313
+ * build-time prerender (the design's "throw or skip" contract permits skip):
314
+ * they write to a discarded synthetic response, so there is nothing to leak.
315
+ * Never set on the live request path.
316
+ */
317
+ _onDemandProducer?: true;
318
+
291
319
  /**
292
320
  * @internal Compiled `cache.searchParams` filter for default cache-key
293
321
  * generation (undefined = "all", the byte-stable unfiltered format). Set
@@ -364,6 +392,22 @@ export interface RequestContext<
364
392
  */
365
393
  _shellForcedMiss?: true;
366
394
 
395
+ /**
396
+ * @internal This request is a router.prerender() warm
397
+ * (prerender/warm-request.ts). In `replace` mode every runtime cache read
398
+ * misses (isWarmReplace); the layers report into the record. A capture's
399
+ * derived context inherits it through the prototype.
400
+ */
401
+ _prerenderWarm?: import("../prerender/warm-request.js").PrerenderWarmRecord;
402
+
403
+ /**
404
+ * @internal Runs a callback in the async context this request entered the
405
+ * handler with (captureRequestEntryContext). router.prerender() dispatches
406
+ * a warm through it when called from inside a request. Absent where the
407
+ * runtime has no AsyncLocalStorage.snapshot.
408
+ */
409
+ _runAtRequestEntry?: <T>(fn: () => T) => T;
410
+
367
411
  /**
368
412
  * @internal The freshness window of the route cache() records this
369
413
  * request read or wrote (cache-scope.ts noteRouteRecordWindow), as ms
@@ -377,7 +421,7 @@ export interface RequestContext<
377
421
 
378
422
  /**
379
423
  * @internal Loader-family snapshot seed for a shell HIT's tail render:
380
- * segment-key -> the capture's elided container (already Flight-deserialized
424
+ * loader id -> the capture's elided container (already Flight-deserialized
381
425
  * by serveShellHit) plus its capture-computed hole bit. resolveLoaderData
382
426
  * overlays it onto the fresh run's container (recorded paths pinned,
383
427
  * hole-marker paths keep the fresh nested promises) so the payload's baked
@@ -385,7 +429,9 @@ export interface RequestContext<
385
429
  * and does not run the loader. Two owners: serveShellHit sets it on the
386
430
  * HIT tail's derived context, and matchPartialWithPprReplay sets it on the
387
431
  * request context of a PPR partial replay once its doc record hits, and
388
- * restores the previous value after the match.
432
+ * restores the previous value after the match. Read through servedPins
433
+ * (loader-cache.ts) only: it also decides which of the record's
434
+ * loader-owned handle pushes stand (loaderPins).
389
435
  */
390
436
  _shellLoaderSeed?: Map<
391
437
  string,
@@ -446,9 +492,7 @@ export interface RequestContext<
446
492
  * scope replaces any route-derived cache() scope for the tail's match
447
493
  * (resolveShellImplicitCacheScope), and a lookup that does not hit throws
448
494
  * ShellRecordUnavailableError instead of resolving segments
449
- * (withCacheLookup): a HIT never runs a handler. The record's
450
- * loader-owned handle pushes restore as authoritative
451
- * (HandleStore.pushRestored): the prelude rendered them.
495
+ * (withCacheLookup): a HIT never runs a handler.
452
496
  */
453
497
  docTail?: true;
454
498
  /**
@@ -458,6 +502,8 @@ export interface RequestContext<
458
502
  * host) and miss the record.
459
503
  */
460
504
  fixedDocKey?: string;
505
+ /** @internal The snapshot's `handles` record (ShellSnapshotHandlesValue). */
506
+ prerenderHandles?: ShellSnapshotHandlesValue;
461
507
  /**
462
508
  * @internal Set ONLY by matchPartialWithPprReplay on the navigation-replay
463
509
  * serve path. Its presence arms the explicit-scope composition in
@@ -807,6 +853,18 @@ export interface RequestContext<
807
853
  */
808
854
  _renderErrors?: unknown[];
809
855
 
856
+ /**
857
+ * @internal Rejections of streamed (loading()) handlers that were recovered
858
+ * into a declared errorBoundary()/notFoundBoundary() fallback, by segment id.
859
+ * The recovered promise resolves, so Flight reports nothing; cacheRoute
860
+ * (cache-scope.ts) refuses a write only when one of ITS segments is listed,
861
+ * the granularity a Flight error row has. The same errors are also pushed to
862
+ * `_renderErrors` for the document cache and PPR shell capture. A render
863
+ * under a derived context (shell capture, SWR re-render) owns its own map.
864
+ * Allocated on the first recovery.
865
+ */
866
+ _recoveredHandlerErrors?: Map<string, unknown>;
867
+
810
868
  /**
811
869
  * @internal Report a non-fatal background error through the router's
812
870
  * onError callback. Wired by the RSC handler / router during request
@@ -857,6 +915,15 @@ export interface RequestContext<
857
915
  * lookups per router (route-map-builder.ts) */
858
916
  _routerId?: string;
859
917
 
918
+ /**
919
+ * @internal Cache versions of the router serving this request. The cache
920
+ * factory builds a store per request without knowing the router, so a store
921
+ * reads its key versions from here, lazily, the same way CFCacheStore
922
+ * resolves its base URL (getCacheVersions, server/build-version-table.ts).
923
+ * Unset on a context no router created; a store then uses the build version.
924
+ */
925
+ _versions?: RouterVersions;
926
+
860
927
  /**
861
928
  * @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
862
929
  * to avoid a second resolveRoute call. Cleared on HMR invalidation.
@@ -916,6 +983,8 @@ export type PublicRequestContext<
916
983
  | "_transitionWhenRefs"
917
984
  | "_pprReplayPostMatchReason"
918
985
  | "_cacheStore"
986
+ | "_prerender"
987
+ | "_onDemandProducer"
919
988
  | "_searchParamsFilter"
920
989
  | "_shellCaptureRun"
921
990
  | "_shellImplicitCache"
@@ -926,6 +995,8 @@ export type PublicRequestContext<
926
995
  | "_resolvedCacheKeys"
927
996
  | "_shellKey"
928
997
  | "_shellForcedMiss"
998
+ | "_prerenderWarm"
999
+ | "_runAtRequestEntry"
929
1000
  | "_routeRecordWindow"
930
1001
  | "_shellFragmentPayload"
931
1002
  | "_shellCaptureGuardTripped"
@@ -947,6 +1018,7 @@ export type PublicRequestContext<
947
1018
  | "_inActionRevalidation"
948
1019
  | "_reportedErrors"
949
1020
  | "_renderErrors"
1021
+ | "_recoveredHandlerErrors"
950
1022
  | "_renderBarrier"
951
1023
  | "_resolveRenderBarrier"
952
1024
  | "_renderBarrierSegmentOrder"
@@ -966,6 +1038,7 @@ export type PublicRequestContext<
966
1038
  | "_tracing"
967
1039
  | "_basename"
968
1040
  | "_routerId"
1041
+ | "_versions"
969
1042
  | "_setStatus"
970
1043
  | "_rotateStateCookie"
971
1044
  | "_setKeepCacheDirective"
@@ -996,6 +1069,22 @@ export const UNTRACKED_BACKGROUND_TASK: unique symbol = Symbol.for(
996
1069
  // AsyncLocalStorage instance for request context
997
1070
  const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
998
1071
 
1072
+ /**
1073
+ * Capture the current async context (RequestContext._runAtRequestEntry), taken
1074
+ * on handler entry before any router scope, so a nested dispatch (a
1075
+ * router.prerender() warm called from a handler, loader or action) does not
1076
+ * inherit the caller's route-definition, loader and cache-scope state. Per
1077
+ * request: workerd refuses a snapshot outside the request that created it.
1078
+ * See docs/design/prerender-every-route.md.
1079
+ */
1080
+ export function captureRequestEntryContext():
1081
+ | (<T>(fn: () => T) => T)
1082
+ | undefined {
1083
+ return typeof AsyncLocalStorage.snapshot === "function"
1084
+ ? AsyncLocalStorage.snapshot()
1085
+ : undefined;
1086
+ }
1087
+
999
1088
  /**
1000
1089
  * Run a function within a request context
1001
1090
  * Used by the RSC handler to provide context to server actions
@@ -1107,6 +1196,12 @@ export interface CreateRequestContextOptions<TEnv> {
1107
1196
  initialResponse?: Response;
1108
1197
  /** Optional cache store for segment caching (used by CacheScope) */
1109
1198
  cacheStore?: SegmentCacheStore;
1199
+ /**
1200
+ * Optional env-scoped writable prerender store + deployment identity (the
1201
+ * durable overlay), resolved per request by the RSC handler from the
1202
+ * `prerender` router option.
1203
+ */
1204
+ prerender?: import("../prerender/on-demand.js").ResolvedPrerender<any>;
1110
1205
  /**
1111
1206
  * Compiled `cache.searchParams` filter from the resolved handler cache
1112
1207
  * config (handler.ts). Stored as _searchParamsFilter.
@@ -1130,8 +1225,13 @@ export interface CreateRequestContextOptions<TEnv> {
1130
1225
  themeConfig?: ResolvedThemeConfig | null;
1131
1226
  /** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
1132
1227
  stateCookieName?: string;
1133
- /** Build version, used as the prefix of a server-rotated rango state value. */
1134
- version?: string;
1228
+ /**
1229
+ * The versions of the router serving this request (resolveRouterVersions).
1230
+ * Stored as `_versions` for the cache stores; `document` is also the prefix
1231
+ * of a server-rotated rango state value, which the browser compares with the
1232
+ * payload's `metadata.version`.
1233
+ */
1234
+ versions?: RouterVersions;
1135
1235
  }
1136
1236
 
1137
1237
  /**
@@ -1152,6 +1252,7 @@ export function createRequestContext<TEnv>(
1152
1252
  variables,
1153
1253
  initialResponse,
1154
1254
  cacheStore,
1255
+ prerender,
1155
1256
  searchParamsFilter,
1156
1257
  explicitTaggedStores,
1157
1258
  cacheProfiles,
@@ -1159,7 +1260,7 @@ export function createRequestContext<TEnv>(
1159
1260
  build = false,
1160
1261
  themeConfig,
1161
1262
  stateCookieName,
1162
- version: stateVersion,
1263
+ versions,
1163
1264
  } = options;
1164
1265
  // ctx.request.headers is guarded like headers() (#976); every router read
1165
1266
  // of this request's headers goes through requestHeaders().
@@ -1194,6 +1295,12 @@ export function createRequestContext<TEnv>(
1194
1295
  // Set-Cookie drain (rsc/helpers.ts) run outside any latched funnel scope,
1195
1296
  // where assertCachedHeaderWriteAllowed is a no-op.
1196
1297
  let rawStubHeaders = stubResponse.headers;
1298
+ // Node's util.inspect unwraps this Proxy and calls undici's Headers hook
1299
+ // with the proxy as `this`. That reads `#headersList` and throws (#1000).
1300
+ // Inspect does not consult the get trap, so the hook is an own property of
1301
+ // the target and formats `rawStubHeaders`. Read on each call: setStatus
1302
+ // rebinds that let to a new Headers.
1303
+ const NODE_INSPECT = Symbol.for("nodejs.util.inspect.custom");
1197
1304
  const guardedStubHeaders: Headers = new Proxy(new Headers(), {
1198
1305
  get(_target, prop) {
1199
1306
  const raw = rawStubHeaders;
@@ -1208,6 +1315,22 @@ export function createRequestContext<TEnv>(
1208
1315
  return (value as (...a: unknown[]) => unknown).bind(raw);
1209
1316
  },
1210
1317
  });
1318
+ Object.defineProperty(guardedStubHeaders, NODE_INSPECT, {
1319
+ value(...args: unknown[]): unknown {
1320
+ const hook = (rawStubHeaders as unknown as Record<symbol, unknown>)[
1321
+ NODE_INSPECT
1322
+ ];
1323
+ if (typeof hook === "function") return hook.apply(rawStubHeaders, args);
1324
+ // DOM Headers has forEach and not entries. Node always has the hook above.
1325
+ const listed: Record<string, string> = {};
1326
+ rawStubHeaders.forEach((value, key) => {
1327
+ listed[key] = value;
1328
+ });
1329
+ return listed;
1330
+ },
1331
+ configurable: true,
1332
+ writable: true,
1333
+ });
1211
1334
  const shadowStubHeaders = (res: Response): void => {
1212
1335
  Object.defineProperty(res, "headers", {
1213
1336
  value: guardedStubHeaders,
@@ -1429,7 +1552,7 @@ export function createRequestContext<TEnv>(
1429
1552
  const prevRaw =
1430
1553
  (requestHeaders(request).get("x-rango-state") || null) ??
1431
1554
  getRawCookieValue(cookieHeader, stateCookieName);
1432
- const value = mintStateValue(stateVersion ?? "0", prevRaw);
1555
+ const value = mintStateValue(versions?.document ?? "0", prevRaw);
1433
1556
  // rawStubHeaders: guard-exempt internal writer — invalidateClientCache()
1434
1557
  // is documented callable from loaders and during shell capture.
1435
1558
  rawStubHeaders.append(
@@ -1505,6 +1628,8 @@ export function createRequestContext<TEnv>(
1505
1628
 
1506
1629
  _handleStore: handleStore,
1507
1630
  _cacheStore: cacheStore,
1631
+ _prerender: prerender,
1632
+ _versions: versions,
1508
1633
  _searchParamsFilter: searchParamsFilter,
1509
1634
  _explicitTaggedStores: explicitTaggedStores,
1510
1635
  _requestTags: new Set<string>(),
@@ -1515,11 +1640,23 @@ export function createRequestContext<TEnv>(
1515
1640
 
1516
1641
  waitUntil(fn: () => Promise<void>): void {
1517
1642
  if (ctx.build) return;
1518
- // Wrap in Promise.resolve().then(fn) so a SYNCHRONOUS throw in a
1643
+ // A deferred task does not rely on the platform carrying the request's
1644
+ // async context into it. Deployed workerd lost it once ("use cache"
1645
+ // background revalidation, cache-runtime.ts); Node and miniflare never
1646
+ // do, so no local run shows a loss. A cache store builds its key inside
1647
+ // the task and reads the serving router's versions and the request host
1648
+ // from the context: without it the write lands under the whole-build
1649
+ // pair, which no router with its own version reads. So the task
1650
+ // re-enters the context it was scheduled under: the store active then
1651
+ // (a derived context), else this one.
1652
+ const scheduledUnder = requestContextStorage.getStore() ?? ctx;
1653
+ // Wrap in Promise.resolve().then(...) so a SYNCHRONOUS throw in a
1519
1654
  // non-async callback becomes a rejected promise handed to the host's
1520
1655
  // waitUntil (logged as a background failure), instead of escaping into
1521
1656
  // the request flow. Mirrors fireAndForgetWaitUntil's deferral.
1522
- const task = Promise.resolve().then(fn);
1657
+ const task = Promise.resolve().then(() =>
1658
+ requestContextStorage.run(scheduledUnder, fn),
1659
+ );
1523
1660
  // Track the task promise so the PPR shell capture can settle the
1524
1661
  // foreground's deferred cache writes before its own match/render (the
1525
1662
  // ordering edge; see _pendingBackgroundTasks). The capture task itself
@@ -7,7 +7,11 @@ import {
7
7
  } from "../browser/react/filter-segment-order.js";
8
8
  import { ThemeProvider } from "../theme/ThemeProvider.js";
9
9
  import { NonceContext } from "../browser/react/nonce-context.js";
10
- import { NavigationStoreContext } from "../browser/react/context.js";
10
+ import {
11
+ HydrationBarrierContext,
12
+ LocationStateContext,
13
+ NavigationStoreContext,
14
+ } from "../browser/react/context.js";
11
15
  import type { NavigationStoreContextValue } from "../browser/react/context.js";
12
16
  import type { HandleData } from "../browser/types.js";
13
17
  import type { ResolvedSegment } from "../types.js";
@@ -119,11 +123,17 @@ function createSsrEventController(opts: {
119
123
  flushRouteState: () => {},
120
124
  setHandleData: () => {},
121
125
  getHandleState: () => handleState,
126
+ lockHydration: () => {},
127
+ getHydrationSnapshot: () => undefined,
128
+ releaseHydrationLock: () => {},
122
129
  setRouteSegmentIds: () => {},
123
130
  setParams: (nextParams) => {
124
131
  params = nextParams;
125
132
  },
126
133
  getParams: () => params,
134
+ commitLocationState: () => {},
135
+ getLocationState: () => undefined,
136
+ takeTreelessLocationState: () => false,
127
137
  setLocation: () => {},
128
138
  startNavigation: () => {
129
139
  throw new Error("Navigation not supported during SSR");
@@ -277,24 +287,28 @@ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
277
287
  }
278
288
 
279
289
  // Create SSR context with request-local pathname/params/handles.
280
- ssrContextValue ??= {
281
- store: null as any,
282
- eventController: createSsrEventController({
290
+ if (!ssrContextValue) {
291
+ const eventController = createSsrEventController({
283
292
  pathname,
284
293
  search,
285
294
  origin,
286
295
  params: resolved.metadata?.params,
287
296
  handleData,
288
297
  matched: resolved.metadata?.matched,
289
- }),
290
- navigate: async () => {},
291
- refresh: async () => {},
292
- version: resolved.metadata?.version,
293
- basename: resolved.metadata?.basename,
294
- };
298
+ });
299
+ ssrContextValue = {
300
+ store: null as any,
301
+ eventController,
302
+ navigate: async () => {},
303
+ refresh: async () => {},
304
+ version: resolved.metadata?.version,
305
+ basename: resolved.metadata?.basename,
306
+ origin: (eventController.getState().location as URL).origin,
307
+ };
308
+ }
295
309
 
296
310
  // Build content tree from segments.
297
- // Order must match NavigationProvider: NavigationStoreContext > NonceContext > ThemeProvider > content
311
+ // Order must match NavigationProvider: NavigationStoreContext > LocationStateContext > NonceContext > ThemeProvider > content
298
312
  // Memoize like payload/handles above: renderSegments is async, so
299
313
  // React.use() on a fresh promise suspends and replays SsrRoot, which
300
314
  // would re-run the entire segment-tree build on every initial render.
@@ -324,10 +338,15 @@ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
324
338
  <NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
325
339
  );
326
340
 
327
- // Wrap with NavigationStoreContext for useNavigation hook
341
+ // Wrap with NavigationStoreContext for useNavigation hook. The server has
342
+ // no history entry: location state is undefined, as readers hydrate.
328
343
  return (
329
344
  <NavigationStoreContext.Provider value={ssrContextValue!}>
330
- {content}
345
+ <LocationStateContext.Provider value={undefined}>
346
+ <HydrationBarrierContext.Provider value={0}>
347
+ {content}
348
+ </HydrationBarrierContext.Provider>
349
+ </LocationStateContext.Provider>
331
350
  </NavigationStoreContext.Provider>
332
351
  );
333
352
  };
@@ -0,0 +1,98 @@
1
+ import { useEffect, useLayoutEffect, useRef, type ReactNode } from "react";
2
+ import {
3
+ auditCommit,
4
+ auditFallback,
5
+ auditHandover,
6
+ auditMount,
7
+ auditRead,
8
+ auditReveal,
9
+ auditUnmount,
10
+ createBoundaryAudit,
11
+ type BoundaryAudit,
12
+ } from "./suspense-audit.js";
13
+
14
+ // Hooks of the suspense audit (docs/internal/suspense-contract.md), called by
15
+ // dev-only components: React Compiler skips a component with a gated hook.
16
+
17
+ function useRecord(boundary: string, segmentId: string): BoundaryAudit {
18
+ const ref = useRef<BoundaryAudit | null>(null);
19
+ // One position can hold another segment's boundary: its Suspense is keyed.
20
+ if (ref.current === null || ref.current.boundary !== boundary) {
21
+ ref.current = createBoundaryAudit(boundary, segmentId);
22
+ }
23
+ return ref.current;
24
+ }
25
+
26
+ // Passive, not layout: React disconnects the layout effects of content a
27
+ // Suspense boundary hides, and that is not an unmount.
28
+ function useMount(rec: BoundaryAudit): void {
29
+ useEffect(() => {
30
+ auditMount(rec);
31
+ return () => auditUnmount(rec);
32
+ }, [rec]);
33
+ }
34
+
35
+ /**
36
+ * One boundary: what each render hands it, what each commit carries, and its
37
+ * mount. Called above the Suspense, which commits while the content waits.
38
+ * `streams` and `nodes` are what else its content waits for (I3 only).
39
+ */
40
+ export function useBoundaryAudit(
41
+ boundary: string,
42
+ segmentId: string,
43
+ value: unknown,
44
+ streams?: Record<string, unknown>,
45
+ nodes?: unknown,
46
+ ): BoundaryAudit {
47
+ const rec = useRecord(boundary, segmentId);
48
+ auditHandover(rec, value);
49
+ useLayoutEffect(() => {
50
+ auditCommit(rec, value, streams, nodes);
51
+ }, [rec, value, streams, nodes]);
52
+ useMount(rec);
53
+ return rec;
54
+ }
55
+
56
+ /** The boundary's content is on screen. */
57
+ export function useRevealAudit(rec: BoundaryAudit): void {
58
+ useLayoutEffect(() => {
59
+ auditReveal(rec);
60
+ }, [rec]);
61
+ }
62
+
63
+ /**
64
+ * One `useLoader` read of a per-loader stream (I1, I2 at the read site). A
65
+ * read that suspends while mounting keeps no record: only a reader on screen
66
+ * is audited.
67
+ */
68
+ export function useReadAudit(loaderId: string, stream: unknown): void {
69
+ const rec = useRecord(`read:${loaderId}`, loaderId);
70
+ auditHandover(rec, stream);
71
+ useLayoutEffect(() => {
72
+ auditRead(rec, stream);
73
+ }, [rec, stream]);
74
+ }
75
+
76
+ /** Wraps a boundary's fallback: mounted means the fallback is on screen. */
77
+ export function AuditedFallback({
78
+ audit,
79
+ children,
80
+ }: {
81
+ audit: BoundaryAudit;
82
+ children: ReactNode;
83
+ }): ReactNode {
84
+ useLayoutEffect(() => auditFallback(audit), [audit]);
85
+ return children;
86
+ }
87
+
88
+ /** Wraps a segment's outlet children: the segment's mount (I4). */
89
+ export function AuditedOutlet({
90
+ segmentId,
91
+ children,
92
+ }: {
93
+ segmentId: string;
94
+ children: ReactNode;
95
+ }): ReactNode {
96
+ useMount(useRecord(`outlet:${segmentId}`, segmentId));
97
+ return children;
98
+ }