@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
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import type { Handler } from "../types.js";
2
+ import type { Handler, InternalHandlerContext } from "../types.js";
3
3
  import type { RouteItem, RouteUseItem, UseItems } from "../route-types.js";
4
4
  import {
5
5
  getUrlPrefix,
@@ -8,7 +8,7 @@ import {
8
8
  requireDslContext,
9
9
  stampStaticDefScope,
10
10
  } from "../server/context";
11
- import { invariant, DataNotFoundError } from "../errors";
11
+ import { invariant, DataNotFoundError, isDevEnvironment } from "../errors";
12
12
  import { validateUserRouteName } from "../route-name.js";
13
13
  import {
14
14
  isPrerenderHandler,
@@ -60,6 +60,41 @@ function resolveResponseType(
60
60
  return options?.[RESPONSE_TYPE];
61
61
  }
62
62
 
63
+ const NO_PRERENDER_DATA_MESSAGE = "No prerender data found for this route";
64
+
65
+ /**
66
+ * A retained on-demand producer must never render on a live production
67
+ * request: bundle eviction is skipped for onDemand routes (the producer body
68
+ * stays available for router.prerender()), so without this gate an
69
+ * overlay+manifest miss on a plain Prerender(..., { onDemand }) route would
70
+ * execute the producer in-request — uncached per-request renders with the
71
+ * personalization guard unarmed. Keep the pre-retention miss contract instead
72
+ * (DataNotFoundError -> 404). Producer runs (build-time bake and
73
+ * router.prerender()) arrive with ctx.build true (createPrerenderContext);
74
+ * Vite dev keeps the documented live fall-through on a dev-store miss, except
75
+ * for a page the overlay marks removed (`_prerenderRemoved`): a live render
76
+ * would show in dev a page production answers with a 404.
77
+ * Pass-through when the route is not onDemand.
78
+ *
79
+ * @internal Exported for the unit test.
80
+ */
81
+ export function gateOnDemandProducer<TEnv>(
82
+ producer: Handler<any, any, TEnv>,
83
+ isOnDemand: boolean,
84
+ ): Handler<any, any, TEnv> {
85
+ if (!isOnDemand) return producer;
86
+ return (ctx) => {
87
+ if (
88
+ !ctx.build &&
89
+ (!isDevEnvironment() ||
90
+ (ctx as InternalHandlerContext<any, TEnv>)._prerenderRemoved)
91
+ ) {
92
+ throw new DataNotFoundError(NO_PRERENDER_DATA_MESSAGE);
93
+ }
94
+ return producer(ctx);
95
+ };
96
+ }
97
+
63
98
  export function createPathHelper<TEnv>(): PathFn<TEnv> {
64
99
  return ((
65
100
  pattern: string,
@@ -117,6 +152,31 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
117
152
 
118
153
  const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${routeName}`;
119
154
 
155
+ // On-demand opt-in lives on the (inner) Prerender def's options for both
156
+ // plain Prerender and Passthrough-wrapped routes. Presence (truthy, not
157
+ // false) marks the route ISR-eligible; the trie od flag + producer retention
158
+ // key off it.
159
+ const onDemandDef = isPassthroughHandler(handler)
160
+ ? handler.prerenderDef
161
+ : isPrerenderHandler(handler)
162
+ ? handler
163
+ : undefined;
164
+ const onDemandOpt = onDemandDef?.options?.onDemand;
165
+ const isOnDemand = onDemandOpt != null && onDemandOpt !== false;
166
+
167
+ // onDemand routes are excluded from the PPR shell lane (a writable refresh
168
+ // cannot atomically replace the captured shell), so ppr has no effect there.
169
+ // Thrown ahead of the forRoute early return so lazy evaluation of another
170
+ // route cannot skip it.
171
+ if (isOnDemand && options?.ppr !== undefined && options.ppr !== false) {
172
+ throw new Error(
173
+ `[rango] Route "${routeName}" sets both ppr and onDemand: onDemand ` +
174
+ `routes are excluded from the PPR shell lane, so ppr has no ` +
175
+ `effect here and the route serves without the shell fast path. ` +
176
+ `Drop one of the two options.`,
177
+ );
178
+ }
179
+
120
180
  if (ctx.forRoute && routeName !== ctx.forRoute) {
121
181
  store.getShortCode("route");
122
182
  return { type: "route" } as RouteItem;
@@ -129,17 +189,16 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
129
189
  ? typeof handler.prerenderDef.handler === "function"
130
190
  ? (handler.prerenderDef.handler as Handler<any, any, TEnv>)
131
191
  : () => {
132
- throw new DataNotFoundError(
133
- "No prerender data found for this route",
134
- );
192
+ throw new DataNotFoundError(NO_PRERENDER_DATA_MESSAGE);
135
193
  }
136
194
  : isPrerenderHandler(handler)
137
195
  ? typeof handler.handler === "function"
138
- ? (handler.handler as Handler<any, any, TEnv>)
196
+ ? gateOnDemandProducer(
197
+ handler.handler as Handler<any, any, TEnv>,
198
+ isOnDemand,
199
+ )
139
200
  : () => {
140
- throw new DataNotFoundError(
141
- "No prerender data found for this route",
142
- );
201
+ throw new DataNotFoundError(NO_PRERENDER_DATA_MESSAGE);
143
202
  }
144
203
  : isStaticHandler(handler)
145
204
  ? (handler.handler as Handler<any, any, TEnv>)
@@ -168,6 +227,7 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
168
227
  prerenderDef: handler as PrerenderHandlerDefinition,
169
228
  }
170
229
  : {}),
230
+ ...(isOnDemand ? { isOnDemand: true as const } : {}),
171
231
  ...(isStaticHandler(handler)
172
232
  ? {
173
233
  isStaticPrerender: true as const,
@@ -14,9 +14,17 @@ import {
14
14
  type ReactNode,
15
15
  } from "react";
16
16
  import { OutletContext, type OutletContextValue } from "./outlet-context.js";
17
- import { loaderStore, type LoaderEntry } from "./loader-store.js";
17
+ import {
18
+ EMPTY_LOADER_SNAPSHOT,
19
+ type LoaderEntry,
20
+ type LoaderStore,
21
+ } from "./loader-store.js";
22
+ import { NavigationStoreContext } from "./browser/react/context.js";
18
23
  import { decodeLoaderEntry } from "./decode-loader-results.js";
19
24
  import { warnAwaitedSsrSuspension } from "./ssr-suspension-warning.js";
25
+ import { SETTLED_THENABLE } from "./thenable-status.js";
26
+ import { useReadAudit } from "./suspense-audit-react.js";
27
+ import { INTERNAL_RANGO_SUSPENSE_AUDIT } from "./internal-suspense-audit.js";
20
28
  import type { LoaderDefinition, LoadOptions } from "./types.js";
21
29
 
22
30
  function isShareableGet(options: LoadOptions | undefined): boolean {
@@ -202,15 +210,18 @@ function useLoaderInternal<T>(
202
210
  }
203
211
  return { contextData: undefined, hasContextData: false };
204
212
  }, [context, loader.$$id]);
205
-
206
213
  // SPIKE (streaming useLoader): a pending per-loader stream suspends HERE —
207
- // the implicit-suspense read. use() is exempt from hook-order rules, so the
208
- // conditional call is legal; on replay after resolution it returns
214
+ // the implicit-suspense read. On replay after resolution use() returns
209
215
  // synchronously and the decoded value takes the contextData slot. Hooks
210
216
  // below never run in a suspended render, so their order is stable across
211
217
  // every COMPLETED render.
212
218
  const { hasContextData } = walk;
213
219
  let contextData = walk.contextData;
220
+ // Dev only (suspense-audit.ts): the stream this reader is handed. The test
221
+ // is a build-time constant, so the hook is called on every render or never.
222
+ if (process.env.NODE_ENV !== "production" && INTERNAL_RANGO_SUSPENSE_AUDIT) {
223
+ useReadAudit(loader.$$id, walk.pendingStream);
224
+ }
214
225
  if (walk.pendingStream) {
215
226
  warnAwaitedSsrSuspension(
216
227
  loader.$$id,
@@ -218,6 +229,10 @@ function useLoaderInternal<T>(
218
229
  walk.pendingStream,
219
230
  );
220
231
  contextData = decodeLoaderEntry(use(walk.pendingStream)) as T;
232
+ } else if (hasContextData) {
233
+ // React logs a render that finishes without use() at a place whose reader
234
+ // suspended in use() on mount, so every read with route context calls it.
235
+ use(SETTLED_THENABLE);
221
236
  }
222
237
 
223
238
  const loaderId = loader.$$id;
@@ -245,10 +260,15 @@ function useLoaderInternal<T>(
245
260
  const bucketKey =
246
261
  effectiveKey === undefined ? loaderId : `${loaderId}::${effectiveKey}`;
247
262
 
263
+ // The router store's loader data (loader-store.ts). None on the server,
264
+ // where the hook reads the route's loader data only.
265
+ const loaders: LoaderStore | undefined = useContext(NavigationStoreContext)
266
+ ?.store?.loaders;
267
+
248
268
  const groupRefetch = useCallback(async (): Promise<void> => {
249
- if (!loaderId) return;
250
- const requestId = loaderStore.reserveRequestId(bucketKey);
251
- loaderStore.beginRequest(bucketKey, requestId);
269
+ if (!loaderId || !loaders) return;
270
+ const requestId = loaders.reserveRequestId(bucketKey);
271
+ loaders.beginRequest(bucketKey, requestId);
252
272
  try {
253
273
  const url = new URL(window.location.href);
254
274
  url.searchParams.set("_rsc_loader", loaderId);
@@ -261,15 +281,15 @@ function useLoaderInternal<T>(
261
281
  if (payload.loaderError) {
262
282
  throw new Error(payload.loaderError.message);
263
283
  }
264
- loaderStore.finishData(bucketKey, requestId, payload.loaderResult);
284
+ loaders.finishData(bucketKey, requestId, payload.loaderResult);
265
285
  } catch (e) {
266
286
  const err = e instanceof Error ? e : new Error(String(e));
267
- loaderStore.finishError(bucketKey, requestId, err);
287
+ loaders.finishError(bucketKey, requestId, err);
268
288
  throw err;
269
289
  } finally {
270
- loaderStore.setLoading(bucketKey, requestId, false);
290
+ loaders.setLoading(bucketKey, requestId, false);
271
291
  }
272
- }, [loaderId, bucketKey]);
292
+ }, [loaderId, bucketKey, loaders]);
273
293
 
274
294
  // Held-navigation pin: a transition commit whose stream for this loader is
275
295
  // still pending announces it (loader-store.ts announcePendingStreams, which
@@ -283,14 +303,17 @@ function useLoaderInternal<T>(
283
303
  snapshot: LoaderEntry;
284
304
  }>(() => ({
285
305
  bucketKey,
286
- snapshot: loaderStore.getSnapshot(bucketKey),
306
+ snapshot: loaders?.getSnapshot(bucketKey) ?? EMPTY_LOADER_SNAPSHOT,
287
307
  }));
308
+ // A new bucket (its key changed) reads nothing from the store during
309
+ // render: it renders empty until the effect below delivers its snapshot.
288
310
  const sharedSnapshot =
289
311
  sharedState.bucketKey === bucketKey
290
312
  ? sharedState.snapshot
291
- : loaderStore.getSnapshot(bucketKey);
313
+ : EMPTY_LOADER_SNAPSHOT;
292
314
  useEffect(() => {
293
- const initial = loaderStore.getSnapshot(bucketKey);
315
+ if (!loaders) return;
316
+ const initial = loaders.getSnapshot(bucketKey);
294
317
  if (initial !== sharedSnapshot) {
295
318
  startTransition(() => {
296
319
  setSharedState({ bucketKey, snapshot: initial });
@@ -299,10 +322,10 @@ function useLoaderInternal<T>(
299
322
  // ephemeral: a reader with no route context has no route-context reset
300
323
  // trigger, so its keyed bucket is reference-counted by the store. A
301
324
  // route-registered reader makes the bucket sticky (reset via clearFamily).
302
- return loaderStore.subscribe(
325
+ return loaders.subscribe(
303
326
  bucketKey,
304
327
  () => {
305
- const next = loaderStore.getSnapshot(bucketKey);
328
+ const next = loaders.getSnapshot(bucketKey);
306
329
  startTransition(() => {
307
330
  setSharedState({ bucketKey, snapshot: next });
308
331
  });
@@ -323,7 +346,7 @@ function useLoaderInternal<T>(
323
346
  // groupKey, and groupRefetch are the only inputs that require a fresh
324
347
  // subscription (groupList is memoized on groupKey; groupRefetch is stable
325
348
  // per bucketKey).
326
- }, [bucketKey, hasContextData, groupKey, groupRefetch]);
349
+ }, [bucketKey, hasContextData, groupKey, groupRefetch, loaders]);
327
350
 
328
351
  // Local state holds the result of:
329
352
  // - parameterized / mutation `load()` calls (load({ params }), POST,
@@ -369,10 +392,10 @@ function useLoaderInternal<T>(
369
392
  lastSharedRequestIdRef.current = null;
370
393
  // Reset every sticky bucket of this loader (keyed or not). Ephemeral
371
394
  // (unregistered keyed) buckets are left to their refcount lifecycle.
372
- loaderStore.clearFamily(loaderId);
395
+ loaders?.clearFamily(loaderId);
373
396
  prevContextDataRef.current = contextData;
374
397
  }
375
- }, [contextData, loaderId]);
398
+ }, [contextData, loaderId, loaders]);
376
399
 
377
400
  // Read priority: a committed parameterized load() result overrides the shared
378
401
  // snapshot; a committed shared snapshot overrides the server-seeded context.
@@ -408,15 +431,17 @@ function useLoaderInternal<T>(
408
431
  const bucket = bucketKeyRef.current;
409
432
  const hasDedicatedBucket = bucket !== id;
410
433
 
411
- const shared = hasDedicatedBucket
412
- ? isShareableGet(loadOptions)
413
- : isPlainRefetch(loadOptions) && hasContextDataRef.current;
434
+ const shared =
435
+ loaders !== undefined &&
436
+ (hasDedicatedBucket
437
+ ? isShareableGet(loadOptions)
438
+ : isPlainRefetch(loadOptions) && hasContextDataRef.current);
414
439
  let sharedRequestId = -1;
415
440
  let localRequestId = -1;
416
441
  if (shared) {
417
- sharedRequestId = loaderStore.reserveRequestId(bucket);
442
+ sharedRequestId = loaders.reserveRequestId(bucket);
418
443
  lastSharedRequestIdRef.current = sharedRequestId;
419
- loaderStore.beginRequest(bucket, sharedRequestId);
444
+ loaders.beginRequest(bucket, sharedRequestId);
420
445
  } else {
421
446
  localRequestId = ++localRequestIdRef.current;
422
447
  setLocalIsLoading(true);
@@ -503,7 +528,7 @@ function useLoaderInternal<T>(
503
528
 
504
529
  const result = payload.loaderResult;
505
530
  if (shared) {
506
- loaderStore.finishData(bucket, sharedRequestId, result);
531
+ loaders.finishData(bucket, sharedRequestId, result);
507
532
  } else if (localRequestId === localRequestIdRef.current) {
508
533
  startTransition(() => {
509
534
  setLocalFetchedData({ has: true, value: result });
@@ -514,7 +539,7 @@ function useLoaderInternal<T>(
514
539
  } catch (e) {
515
540
  const err = e instanceof Error ? e : new Error(String(e));
516
541
  if (shared) {
517
- loaderStore.finishError(bucket, sharedRequestId, err);
542
+ loaders.finishError(bucket, sharedRequestId, err);
518
543
  } else if (localRequestId === localRequestIdRef.current) {
519
544
  setLocalError(err);
520
545
  setLocalIsLoading(false);
@@ -525,11 +550,11 @@ function useLoaderInternal<T>(
525
550
  return dataRef.current as T;
526
551
  } finally {
527
552
  if (shared) {
528
- loaderStore.setLoading(bucket, sharedRequestId, false);
553
+ loaders.setLoading(bucket, sharedRequestId, false);
529
554
  }
530
555
  }
531
556
  },
532
- [throwOnError],
557
+ [throwOnError, loaders],
533
558
  );
534
559
 
535
560
  if (throwOnError) {
@@ -708,8 +733,10 @@ export function useFetchLoader<T>(
708
733
  export function useRefreshLoaders(): (
709
734
  groups: string | string[],
710
735
  ) => Promise<void> {
736
+ const loaders = useContext(NavigationStoreContext)?.store?.loaders;
711
737
  return useCallback(
712
- (groups: string | string[]) => loaderStore.refreshGroups(groups),
713
- [],
738
+ (groups: string | string[]) =>
739
+ loaders ? loaders.refreshGroups(groups) : Promise.resolve(),
740
+ [loaders],
714
741
  );
715
742
  }