@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
@@ -92,15 +92,25 @@
92
92
  * - Action context (if POST)
93
93
  */
94
94
  import type { InternalHandlerContext, ResolvedSegment } from "../../types.js";
95
- import type { OwnedPushDelivery } from "../../cache/handle-snapshot.js";
96
- import type { EntryData } from "../../server/context.js";
95
+ import { isPprEntry, type EntryData } from "../../server/context.js";
97
96
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
98
97
  import { getRouterContext, type RouterContext } from "../router-context.js";
99
98
  import { observeEvent } from "../instrument.js";
100
99
  import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
101
100
  import { treeHasStreaming } from "./segment-resolution.js";
102
- import { liveLaneLoaderIds } from "../segment-resolution/loader-cache.js";
101
+ import { loaderPins } from "../segment-resolution/loader-cache.js";
103
102
  import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
103
+ import {
104
+ isStoredEntryStale,
105
+ readVerifiedStoredEntry,
106
+ serializePrerenderKey,
107
+ type PrerenderKey,
108
+ } from "../../prerender/writable-store.js";
109
+ import { IN_FLIGHT_LEADER_MAX_WAIT_MS } from "../../cache/cache-policy.js";
110
+ import type {
111
+ PrerenderTargetObject,
112
+ ResolvedPrerender,
113
+ } from "../../prerender/on-demand.js";
104
114
  import {
105
115
  _getRequestContext,
106
116
  type RequestContext,
@@ -112,6 +122,7 @@ import {
112
122
  import { ShellRecordUnavailableError } from "../../cache/shell-snapshot.js";
113
123
  import { prerenderStoreShortCircuits } from "../navigation-snapshot.js";
114
124
  import { paramsEqual } from "../params-util.js";
125
+ import { requestHeaders } from "../../server/request-headers.js";
115
126
 
116
127
  // Lazily initialized prerender store singleton and dynamically imported deps.
117
128
  // Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
@@ -129,10 +140,27 @@ let _restoreHandles:
129
140
  let _decodeHandles:
130
141
  | typeof import("../../cache/handle-snapshot.js").decodeHandles
131
142
  | undefined;
143
+ let _restoreRecordHandles:
144
+ | typeof import("../../cache/handle-snapshot.js").restoreRecordHandles
145
+ | undefined;
132
146
  let _hashParams:
133
147
  | typeof import("../../prerender/param-hash.js").hashParams
134
148
  | undefined;
135
149
 
150
+ /**
151
+ * @internal Replace the prerender store this module serves from, and return
152
+ * the one it replaced. For serveShellRequest (testing/serve-shell-request.ts),
153
+ * which installs the production store over the artifacts it bakes for a call
154
+ * and puts the previous one back after it.
155
+ */
156
+ export function setPrerenderStoreForTests(
157
+ store: PrerenderStore | null | undefined,
158
+ ): PrerenderStore | null | undefined {
159
+ const previous = prerenderStoreInstance;
160
+ prerenderStoreInstance = store;
161
+ return previous;
162
+ }
163
+
136
164
  async function ensurePrerenderDeps() {
137
165
  if (!_deserializeSegments) {
138
166
  const [codec, snapshot, paramHash, store] = await Promise.all([
@@ -145,6 +173,7 @@ async function ensurePrerenderDeps() {
145
173
  _fragmentSegments = codec.fragmentSegments;
146
174
  _restoreHandles = snapshot.restoreHandles;
147
175
  _decodeHandles = snapshot.decodeHandles;
176
+ _restoreRecordHandles = snapshot.restoreRecordHandles;
148
177
  _hashParams = paramHash.hashParams;
149
178
  if (prerenderStoreInstance === undefined) {
150
179
  prerenderStoreInstance = store.createPrerenderStore();
@@ -241,12 +270,21 @@ async function* yieldFromStore<TEnv>(
241
270
  reqCtx: RequestContext<TEnv> | undefined,
242
271
  resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
243
272
  resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
273
+ /**
274
+ * The entry may be newer than the client's copy: an on-demand overlay entry
275
+ * that a refresh (possibly inside this very action) replaced. On an action
276
+ * the route's own segments are then re-sent, as a live action re-render
277
+ * re-sends them (revalidation.ts "action:route-segment"); the immutable
278
+ * bundled manifest keeps the client's copy.
279
+ */
280
+ replaceable?: boolean,
244
281
  ): AsyncGenerator<ResolvedSegment> {
245
282
  if (
246
283
  !_deserializeSegments ||
247
284
  !_fragmentSegments ||
248
285
  !_restoreHandles ||
249
286
  !_decodeHandles ||
287
+ !_restoreRecordHandles ||
250
288
  !_hashParams
251
289
  ) {
252
290
  throw new Error("yieldFromStore called before ensurePrerenderDeps");
@@ -271,6 +309,16 @@ async function* yieldFromStore<TEnv>(
271
309
  }
272
310
  }
273
311
 
312
+ // Before the render barrier freezes the document snapshot.
313
+ const shellHandles = reqCtx?._shellImplicitCache?.prerenderHandles;
314
+ if (handleStore && shellHandles) {
315
+ await _restoreRecordHandles!(
316
+ handleStore,
317
+ shellHandles,
318
+ loaderPins(ctx.entries, reqCtx),
319
+ );
320
+ }
321
+
274
322
  state.cacheHit = true;
275
323
  state.cacheSource = "prerender";
276
324
  state.cachedSegments = segments;
@@ -293,11 +341,16 @@ async function* yieldFromStore<TEnv>(
293
341
  // different content, so we must NOT nullify.
294
342
  const paramsChanged =
295
343
  !ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
344
+ const resendRouteSegments = replaceable === true && ctx.isAction;
296
345
  for (const segment of segments) {
297
346
  if (
298
347
  !ctx.isFullMatch &&
299
348
  !paramsChanged &&
300
- ctx.clientSegmentSet.has(segment.id)
349
+ ctx.clientSegmentSet.has(segment.id) &&
350
+ !(
351
+ resendRouteSegments &&
352
+ (segment.type === "route" || segment.belongsToRoute === true)
353
+ )
301
354
  ) {
302
355
  keepClientSegment(segment);
303
356
  }
@@ -346,11 +399,9 @@ export async function prerenderEntryExists(
346
399
  ).createPrerenderStore();
347
400
  }
348
401
  if (!prerenderStoreInstance) return false;
349
- // Non-intercept variant only: whether the navigation IS an intercept (and
350
- // therefore whether tryPrerenderLookup reads `paramHash + "/i"`) resolves
351
- // during the match, so this pre-match fast path can only guess the normal
352
- // artifact. A wrong guess is reclassified post-match from the match
353
- // pipeline's `_pprReplayPostMatchReason` stamp.
402
+ // Non-intercept variant only: whether the navigation is an intercept (which
403
+ // reads `paramHash + "/i"`) resolves during the match. A wrong guess is
404
+ // reclassified post-match from `_pprReplayPostMatchReason`.
354
405
  const entry = await prerenderStoreInstance.get(
355
406
  routeKey,
356
407
  _hashParams!(params),
@@ -364,6 +415,63 @@ export async function prerenderEntryExists(
364
415
  return entry != null;
365
416
  }
366
417
 
418
+ /**
419
+ * Serialized overlay key -> start time of its in-flight `onRevalidate`.
420
+ * Dedupes the obvious Node wiring, `(t, env) => router.prerender(t, { env })`,
421
+ * and queue sends alike, per isolate. Entries older than
422
+ * IN_FLIGHT_LEADER_MAX_WAIT_MS count as free: a never-settling onRevalidate
423
+ * (hung queue send) or a killed execution context never runs `.finally`, and
424
+ * would otherwise pin the key for the isolate's lifetime.
425
+ */
426
+ const overlayRevalidationsInFlight = new Map<string, number>();
427
+
428
+ /** @internal Tests only (testing/serve-shell-request.ts resetShellTestState). */
429
+ export function resetOverlayRevalidationsForTests(): void {
430
+ overlayRevalidationsInFlight.clear();
431
+ }
432
+
433
+ /** @internal Exported for the eviction unit test. */
434
+ export function scheduleOverlayRevalidation<TEnv>(
435
+ serializedKey: string,
436
+ onRevalidate: NonNullable<ResolvedPrerender["config"]["onRevalidate"]>,
437
+ target: PrerenderTargetObject,
438
+ reqCtx: RequestContext<TEnv>,
439
+ now: number = Date.now(),
440
+ ): void {
441
+ // waitUntil is a no-op under build contexts: nothing would clear the key.
442
+ if (reqCtx.build) return;
443
+ const startedAt = overlayRevalidationsInFlight.get(serializedKey);
444
+ if (
445
+ startedAt !== undefined &&
446
+ now - startedAt < IN_FLIGHT_LEADER_MAX_WAIT_MS
447
+ ) {
448
+ return;
449
+ }
450
+ overlayRevalidationsInFlight.set(serializedKey, now);
451
+ const env = reqCtx.env;
452
+ const executionContext = reqCtx.executionContext;
453
+ const reportError = reqCtx._reportBackgroundError;
454
+ // The stale entry still serves this request; this only schedules.
455
+ reqCtx.waitUntil(() =>
456
+ Promise.resolve()
457
+ .then(() => onRevalidate(target, env, executionContext))
458
+ .then(
459
+ () => {},
460
+ (err) => {
461
+ // A failed onRevalidate must not fail the response, but must reach
462
+ // onError like the runtime cache's stale-revalidation failures.
463
+ reportError?.(err, "stale-revalidation");
464
+ },
465
+ )
466
+ .finally(() => {
467
+ // An evicted-and-rescheduled entry belongs to the newer run.
468
+ if (overlayRevalidationsInFlight.get(serializedKey) === now) {
469
+ overlayRevalidationsInFlight.delete(serializedKey);
470
+ }
471
+ }),
472
+ );
473
+ }
474
+
367
475
  /**
368
476
  * Look up a prerendered (build-time cached) entry for the current route and, on
369
477
  * a hit, yield its segments. Returns true when an entry was served (the caller
@@ -379,31 +487,99 @@ async function* tryPrerenderLookup<TEnv>(
379
487
  reqCtx: RequestContext<TEnv> | undefined,
380
488
  resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
381
489
  resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
490
+ isPassthroughPrerenderRoute: boolean,
491
+ overlay?: ResolvedPrerender,
382
492
  ): AsyncGenerator<ResolvedSegment, boolean> {
383
- const paramHash = _hashParams!(ctx.matched.params);
384
- const isPassthroughPrerenderRoute = ctx.entries.some(
385
- (entry) => entry.type === "route" && entry.isPassthrough === true,
386
- );
387
- const lookupHash = ctx.isIntercept ? paramHash + "/i" : paramHash;
388
- const entry = await prerenderStoreInstance!.get(
389
- ctx.matched.routeKey,
390
- lookupHash,
391
- {
392
- pathname: ctx.pathname,
393
- isPassthroughRoute: isPassthroughPrerenderRoute,
394
- },
395
- );
396
- if (!entry) return false;
397
- yield* yieldFromStore(
398
- entry,
399
- ctx,
400
- state,
401
- pipelineStart,
402
- reqCtx,
403
- resolveLoadersOnly,
404
- resolveLoadersOnlyWithRevalidation,
405
- );
406
- return true;
493
+ // 1. Writable durable overlay (per-request, env-scoped), read before the
494
+ // bundled manifest: it is always newer. Only on-demand routes are read
495
+ // (router.prerender() refuses non-od routes, so a pr-only route always
496
+ // misses), and never for intercept navigations (only the main variant is
497
+ // written). Actions read it too: a plain on-demand route has no live
498
+ // handler, so skipping the overlay would 404 an overlay-only param
499
+ // (gateOnDemandProducer). A store error degrades to a miss
500
+ // (readVerifiedStoredEntry); a stale hit serves and may schedule onRevalidate.
501
+ if (overlay && ctx.matched.od && !ctx.isIntercept) {
502
+ const key: PrerenderKey = {
503
+ routerId: overlay.routerId,
504
+ version: overlay.version,
505
+ routeName: ctx.matched.routeKey,
506
+ paramHash: _hashParams!(ctx.matched.params),
507
+ };
508
+ const stored = await readVerifiedStoredEntry(
509
+ overlay.config.store,
510
+ key,
511
+ ctx.matched.params,
512
+ );
513
+ if (stored) {
514
+ // A stale "removed" marker is rechecked like a stale page: only a
515
+ // refresh that hit notFound() (or declined) stamps one with a ttl or
516
+ // tags. A prerender.remove() marker has neither and never gets here.
517
+ if (
518
+ overlay.config.onRevalidate &&
519
+ reqCtx &&
520
+ isStoredEntryStale(stored, Date.now())
521
+ ) {
522
+ scheduleOverlayRevalidation(
523
+ serializePrerenderKey(key),
524
+ overlay.config.onRevalidate,
525
+ {
526
+ route: ctx.matched.routeKey,
527
+ params: ctx.matched.params,
528
+ } as PrerenderTargetObject,
529
+ reqCtx,
530
+ );
531
+ }
532
+ if (stored.removed === true) {
533
+ // The page was removed. Not served, and step 2 is skipped: the
534
+ // build-time entry below must not come back. The route's handler
535
+ // answers: a Passthrough route's live handler, or a plain route's
536
+ // gated producer, which `_prerenderRemoved` makes a 404 in dev too
537
+ // (gateOnDemandProducer).
538
+ (
539
+ ctx.handlerContext as InternalHandlerContext<any, TEnv>
540
+ )._prerenderRemoved = true;
541
+ return false;
542
+ }
543
+ yield* yieldFromStore(
544
+ stored.entry,
545
+ ctx,
546
+ state,
547
+ pipelineStart,
548
+ reqCtx,
549
+ resolveLoadersOnly,
550
+ resolveLoadersOnlyWithRevalidation,
551
+ true,
552
+ );
553
+ return true;
554
+ }
555
+ }
556
+
557
+ // 2. Bundled build manifest (immutable). Absent for on-demand-only routes.
558
+ if (prerenderStoreInstance) {
559
+ const paramHash = _hashParams!(ctx.matched.params);
560
+ const entry = await prerenderStoreInstance.get(
561
+ ctx.matched.routeKey,
562
+ ctx.isIntercept ? paramHash + "/i" : paramHash,
563
+ {
564
+ pathname: ctx.pathname,
565
+ isPassthroughRoute: isPassthroughPrerenderRoute,
566
+ },
567
+ );
568
+ if (entry) {
569
+ yield* yieldFromStore(
570
+ entry,
571
+ ctx,
572
+ state,
573
+ pipelineStart,
574
+ reqCtx,
575
+ resolveLoadersOnly,
576
+ resolveLoadersOnlyWithRevalidation,
577
+ );
578
+ return true;
579
+ }
580
+ }
581
+
582
+ return false;
407
583
  }
408
584
 
409
585
  /**
@@ -464,6 +640,8 @@ export function withCacheLookup<TEnv>(
464
640
  ? "intercept"
465
641
  : undefined;
466
642
  }
643
+ // Per-request writable prerender overlay (durable), resolved by the handler.
644
+ const prerenderOverlay = pipelineReqCtx?._prerender;
467
645
 
468
646
  const {
469
647
  evaluateRevalidation,
@@ -474,20 +652,30 @@ export function withCacheLookup<TEnv>(
474
652
  resolveAllSegmentsWithRevalidation,
475
653
  } = getRouterContext<TEnv>();
476
654
 
477
- if (prerenderStoreShortCircuits(ctx.matched.pr, ctx.request)) {
655
+ // An on-demand route may have no build-baked entry yet still needs a
656
+ // writable-overlay lookup, so od joins pr in the gate. The retained producer
657
+ // is NEVER run by this pipeline: a miss falls through like pr + miss.
658
+ const isPassthroughPrerenderRoute = ctx.entries.some(
659
+ (entry) => entry.type === "route" && entry.isPassthrough === true,
660
+ );
661
+ const overlayEligible =
662
+ ctx.matched.od === true && !requestHeaders(ctx.request).get("X-RSC-HMR");
663
+ if (
664
+ prerenderStoreShortCircuits(ctx.matched.pr, ctx.request) ||
665
+ overlayEligible
666
+ ) {
478
667
  // Actions normally re-render fresh and skip the prerender store. But a pure
479
668
  // Prerender route's handler is evicted at build, so there is no fresh
480
669
  // handler to run on an action re-render -- without the fallback the
481
670
  // re-render falls through to the evicted handler and throws "No prerender
482
671
  // data found". Serve the prerendered entry instead (the action ran already;
483
- // its result is applied client-side via useActionState). Passthrough routes
484
- // keep a liveHandler, so they still re-render fresh on actions.
485
- const isPassthroughPrerenderRoute = ctx.entries.some(
486
- (entry) => entry.type === "route" && entry.isPassthrough === true,
487
- );
672
+ // its result is applied client-side via useActionState). A plain on-demand
673
+ // route is the same case, served from the overlay first (an overlay entry
674
+ // is newer than the build one, and may be the only one). Passthrough
675
+ // routes keep a liveHandler, so they still re-render fresh on actions.
488
676
  if (!ctx.isAction || !isPassthroughPrerenderRoute) {
489
677
  await ensurePrerenderDeps();
490
- if (prerenderStoreInstance) {
678
+ if (prerenderStoreInstance || prerenderOverlay) {
491
679
  const served = yield* tryPrerenderLookup(
492
680
  ctx,
493
681
  state,
@@ -495,6 +683,8 @@ export function withCacheLookup<TEnv>(
495
683
  pipelineReqCtx,
496
684
  resolveLoadersOnly,
497
685
  resolveLoadersOnlyWithRevalidation,
686
+ isPassthroughPrerenderRoute,
687
+ prerenderOverlay,
498
688
  );
499
689
  if (served) return;
500
690
  }
@@ -519,6 +709,7 @@ export function withCacheLookup<TEnv>(
519
709
  pipelineReqCtx,
520
710
  resolveLoadersOnly,
521
711
  resolveLoadersOnlyWithRevalidation,
712
+ isPassthroughPrerenderRoute,
522
713
  );
523
714
  if (served) return;
524
715
  }
@@ -546,20 +737,14 @@ export function withCacheLookup<TEnv>(
546
737
  return;
547
738
  }
548
739
 
549
- // A record's loader-owned handle values claim their loader, so its own
550
- // cache() HIT does not replay them again (restoreHandles). A document
551
- // HIT tail restores a bake-lane owner's values as the prelude's, unclaimed:
552
- // they stand against any later push of that loader
553
- // (HandleStore.pushRestored). An owner the route runs on the live lane is
554
- // a hole, so its live run still replaces them, even inside a restored
555
- // loader's body.
556
- const ownedPushes: OwnedPushDelivery = {
557
- claim: (ctx.handlerContext as InternalHandlerContext)._claimLoaderPushes,
558
- };
559
- if (tailMarker) {
560
- ownedPushes.liveLane = liveLaneLoaderIds(ctx.entries);
561
- ownedPushes.restore = tailMarker.docTail === true;
562
- }
740
+ // Only a ppr route's records hold loader pushes, and only its shell pins
741
+ // loaders (CachedEntryData.handleOwners, the loader seed): any other
742
+ // route's record restores as a plain replay.
743
+ const leaf = ctx.entries[ctx.entries.length - 1];
744
+ const ownedPushes =
745
+ leaf !== undefined && isPprEntry(leaf)
746
+ ? () => loaderPins(ctx.entries, pipelineReqCtx)
747
+ : undefined;
563
748
  const explicitLookup = await ctx.cacheScope.lookupRouteDetailed(
564
749
  ctx.pathname,
565
750
  ctx.matched.params,
@@ -38,12 +38,31 @@ export interface ResolveNavigationDeps {
38
38
  * never produce a partial match, so the replay path must decide
39
39
  * `no-navigation-context` BEFORE spending shell-store reads — the two
40
40
  * predicates drifting apart would misattribute the bypass again.
41
+ *
42
+ * `requestOrigin` is the origin pathname matching uses (`url.origin`), which
43
+ * may not be `request.url`'s. `X-RSC-Router-Client-Path` is returned as sent.
44
+ * A Referer counts only when it resolves against that origin and stays there.
45
+ * A cross-origin Referer is dropped: resolveNavigation matches only the
46
+ * pathname, so a foreign URL would be treated as a route on this app. A value
47
+ * that does not parse is no context either.
41
48
  */
42
- export function getNavigationContextHeader(request: Request): string | null {
43
- return (
44
- requestHeaders(request).get("X-RSC-Router-Client-Path") ||
45
- requestHeaders(request).get("Referer")
46
- );
49
+ export function getNavigationContextHeader(
50
+ request: Request,
51
+ requestOrigin: string,
52
+ ): string | null {
53
+ const headers = requestHeaders(request);
54
+ const clientPath = headers.get("X-RSC-Router-Client-Path");
55
+ if (clientPath) return clientPath;
56
+
57
+ const referer = headers.get("Referer");
58
+ if (!referer) return null;
59
+
60
+ try {
61
+ if (new URL(referer, requestOrigin).origin !== requestOrigin) return null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ return referer;
47
66
  }
48
67
 
49
68
  /**
@@ -84,7 +103,7 @@ export async function resolveNavigation(
84
103
  const clientSegmentIds =
85
104
  url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
86
105
  const stale = url.searchParams.get("_rsc_stale") === "true";
87
- const previousUrl = getNavigationContextHeader(request);
106
+ const previousUrl = getNavigationContextHeader(request, url.origin);
88
107
  const interceptSourceUrl = getInterceptSourceHeader(request);
89
108
  const isHmr = !!requestHeaders(request).get("X-RSC-HMR");
90
109
 
@@ -330,6 +330,8 @@ export interface RouteMatchResult<TEnv = any> {
330
330
  pr?: true;
331
331
  /** Passthrough: handler kept for live fallback on unknown params (from trie) */
332
332
  pt?: true;
333
+ /** On-demand prerender eligible (router.prerender() refreshable) */
334
+ od?: true;
333
335
  /** Response type for non-RSC routes (json, text, image, any) */
334
336
  responseType?: string;
335
337
  /** Negotiate variants: response-type routes sharing this path */
@@ -455,6 +457,9 @@ export function findMatch<TEnv>(
455
457
  const ptFlag = entry.passthroughRouteKeys?.has(routeKey)
456
458
  ? { pt: true as const }
457
459
  : {};
460
+ const odFlag = entry.onDemandRouteKeys?.has(routeKey)
461
+ ? { od: true as const }
462
+ : {};
458
463
 
459
464
  const match = regex.exec(pathname);
460
465
  if (match) {
@@ -484,6 +489,7 @@ export function findMatch<TEnv>(
484
489
  redirectTo: pathname + "/",
485
490
  ...prFlag,
486
491
  ...ptFlag,
492
+ ...odFlag,
487
493
  };
488
494
  } else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
489
495
  return {
@@ -493,6 +499,7 @@ export function findMatch<TEnv>(
493
499
  redirectTo: pathname.slice(0, -1),
494
500
  ...prFlag,
495
501
  ...ptFlag,
502
+ ...odFlag,
496
503
  };
497
504
  }
498
505
 
@@ -502,6 +509,7 @@ export function findMatch<TEnv>(
502
509
  params,
503
510
  ...prFlag,
504
511
  ...ptFlag,
512
+ ...odFlag,
505
513
  };
506
514
  }
507
515
 
@@ -520,6 +528,7 @@ export function findMatch<TEnv>(
520
528
  params,
521
529
  ...prFlag,
522
530
  ...ptFlag,
531
+ ...odFlag,
523
532
  };
524
533
  } else if (trailingSlashMode === "never") {
525
534
  if (pathnameHasTrailingSlash) {
@@ -530,6 +539,7 @@ export function findMatch<TEnv>(
530
539
  redirectTo: alternatePathname,
531
540
  ...prFlag,
532
541
  ...ptFlag,
542
+ ...odFlag,
533
543
  };
534
544
  }
535
545
  return {
@@ -538,6 +548,7 @@ export function findMatch<TEnv>(
538
548
  params,
539
549
  ...prFlag,
540
550
  ...ptFlag,
551
+ ...odFlag,
541
552
  };
542
553
  } else if (trailingSlashMode === "always") {
543
554
  if (!pathnameHasTrailingSlash) {
@@ -548,6 +559,7 @@ export function findMatch<TEnv>(
548
559
  redirectTo: alternatePathname,
549
560
  ...prFlag,
550
561
  ...ptFlag,
562
+ ...odFlag,
551
563
  };
552
564
  }
553
565
  return {
@@ -556,6 +568,7 @@ export function findMatch<TEnv>(
556
568
  params,
557
569
  ...prFlag,
558
570
  ...ptFlag,
571
+ ...odFlag,
559
572
  };
560
573
  } else {
561
574
  const canonicalPath = hasTrailingSlash
@@ -568,6 +581,7 @@ export function findMatch<TEnv>(
568
581
  redirectTo: canonicalPath,
569
582
  ...prFlag,
570
583
  ...ptFlag,
584
+ ...odFlag,
571
585
  };
572
586
  }
573
587
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Resolve the router-wide default Link prefetch strategy once, at router init.
3
- * The resolved value ships to the client in payload metadata; the browser
4
- * entry applies it via `setDefaultPrefetchStrategy` (browser/prefetch/
5
- * default-strategy.ts) and every `<Link>` without an explicit `prefetch` prop
6
- * falls back to it.
3
+ * The resolved value ships to the client in payload metadata; the router
4
+ * store carries it (`NavigationStoreContext.defaultPrefetch`, see
5
+ * browser/prefetch/default-strategy.ts) and every `<Link>` without an explicit
6
+ * `prefetch` prop falls back to it.
7
7
  *
8
8
  * The type lives HERE (not in browser/react/Link.tsx) because both seats need
9
9
  * it: this server-side resolver and the client Link/default-strategy modules.