@rangojs/router 0.16.0 → 0.18.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 (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -45,7 +45,8 @@ import {
45
45
  } from "./helpers.js";
46
46
  import { applyViewTransitionDefault } from "./view-transition-default.js";
47
47
  import { getRouterContext } from "../router-context.js";
48
- import { observeEvent, observeHandler } from "../instrument.js";
48
+ import { observeEvent, observeSegmentHandler } from "../instrument.js";
49
+ import { runInSegmentTagScope } from "../../cache/cache-tag.js";
49
50
  import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
50
51
  import {
51
52
  track,
@@ -215,13 +216,14 @@ export async function resolveLoadersWithRevalidation<TEnv>(
215
216
  // onError/loader.error. isPartial flags the reporting phase accordingly.
216
217
  const errorContext = { ...buildLoaderErrorContext(ctx), isPartial: true };
217
218
 
218
- // PPR lane decision, same rule as the fresh funnel (fresh.ts): an entry
219
- // without renderable loading() puts its loaders on the BAKE lane. The key
220
- // must thread through EVERY resolveLoaderData funnel — omitting it here made
221
- // a capture-active context whole-container-mask every loader on this path
222
- // (a never-settling promise for a loader that should have executed).
219
+ // PPR bake/seed key, same as fresh.ts (lane rule: see resolveLoaderData,
220
+ // loader-cache.ts). The key must thread through EVERY resolveLoaderData
221
+ // funnel — omitting it here made a capture-active context
222
+ // whole-container-mask every loader on this path (a never-settling promise
223
+ // for a loader that should have executed).
223
224
  // Outside capture the key only activates the _shellLoaderSeed overlay,
224
- // which document-only serveShellHit seeds — inert for partial requests.
225
+ // which serveShellHit seeds for a document HIT and the PPR partial replay
226
+ // (matchPartialWithPprReplay) seeds once its doc record hits.
225
227
  const bakeLane = !entryLoadingMasksLoaders(entry.loading);
226
228
 
227
229
  const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
@@ -240,9 +242,10 @@ export async function resolveLoadersWithRevalidation<TEnv>(
240
242
  loaderEntry,
241
243
  ctx,
242
244
  ctx.pathname,
243
- bakeLane || loaderEntry.awaitBeforeFlush === true
244
- ? segmentId
245
- : null,
245
+ // `bake`, not awaitBeforeFlush: a navigation evaluation never
246
+ // carries awaitBeforeFlush, and an ssr: false loader must pin
247
+ // here exactly as on the document HIT.
248
+ bakeLane || loaderEntry.bake === true ? segmentId : null,
246
249
  ),
247
250
  ),
248
251
  entry,
@@ -476,7 +479,9 @@ async function resolveParallelSlotComponent<TEnv>(args: {
476
479
  handlerRan = true;
477
480
  if (hasLoadingFallback) {
478
481
  const result =
479
- typeof handler === "function" ? handler(context) : handler;
482
+ typeof handler === "function"
483
+ ? runInSegmentTagScope(parallelId, handler, context)
484
+ : handler;
480
485
  if (result instanceof Promise) {
481
486
  warnOnStreamedResponse(result, parallelId);
482
487
  const tracked = deps.trackHandler(result, {
@@ -497,7 +502,9 @@ async function resolveParallelSlotComponent<TEnv>(args: {
497
502
  }
498
503
  } else {
499
504
  component =
500
- typeof handler === "function" ? await handler(context) : handler;
505
+ typeof handler === "function"
506
+ ? await runInSegmentTagScope(parallelId, handler, context)
507
+ : handler;
501
508
  }
502
509
  }
503
510
  }
@@ -842,14 +849,19 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
842
849
  : routeEntry.handler;
843
850
  if (!routeEntry.loading) {
844
851
  const result = handleHandlerResult(
845
- await observeHandler(entry.id, handler, context),
852
+ await observeSegmentHandler(
853
+ entry.shortCode,
854
+ entry.id,
855
+ handler,
856
+ context,
857
+ ),
846
858
  );
847
859
  doneHandler();
848
860
  return result;
849
861
  }
850
862
  if (!actionContext) {
851
863
  const result = handleHandlerResult(
852
- observeHandler(entry.id, handler, context),
864
+ observeSegmentHandler(entry.shortCode, entry.id, handler, context),
853
865
  );
854
866
  if (result instanceof Promise) {
855
867
  warnOnStreamedResponse(result, routeEntry.id);
@@ -875,7 +887,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
875
887
  entryId: entry.id,
876
888
  });
877
889
  const actionResult = handleHandlerResult(
878
- await observeHandler(entry.id, handler, context),
890
+ await observeSegmentHandler(
891
+ entry.shortCode,
892
+ entry.id,
893
+ handler,
894
+ context,
895
+ ),
879
896
  );
880
897
  doneHandler();
881
898
  return {
@@ -934,6 +951,8 @@ export async function resolveSegmentWithRevalidation<TEnv>(
934
951
  deps: SegmentResolutionDeps<TEnv>,
935
952
  actionContext?: ActionContext,
936
953
  stale?: boolean,
954
+ /** The matched route's full chain; see ResolveSegmentOptions.chain (fresh.ts). */
955
+ chain?: readonly EntryData[],
937
956
  ): Promise<SegmentRevalidationResult> {
938
957
  const segments: ResolvedSegment[] = [];
939
958
  const matchedIds: string[] = [];
@@ -1066,6 +1085,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
1066
1085
  matchedIds.push(...parallelResult.matchedIds);
1067
1086
 
1068
1087
  for (const orphan of entry.layout) {
1088
+ if (chain?.includes(orphan)) continue;
1069
1089
  const orphanResult = await resolveOrphanLayoutWithRevalidation(
1070
1090
  orphan,
1071
1091
  params,
@@ -1274,6 +1294,27 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1274
1294
  segments.push(...parallelResult.segments);
1275
1295
  matchedIds.push(...parallelResult.matchedIds);
1276
1296
 
1297
+ // Nested routeless entries; see resolveOrphanLayout (fresh.ts).
1298
+ for (const nested of orphan.layout) {
1299
+ const nestedResult = await resolveOrphanLayoutWithRevalidation(
1300
+ nested,
1301
+ params,
1302
+ context,
1303
+ clientSegmentIds,
1304
+ prevParams,
1305
+ request,
1306
+ prevUrl,
1307
+ nextUrl,
1308
+ routeKey,
1309
+ belongsToRoute,
1310
+ deps,
1311
+ actionContext,
1312
+ stale,
1313
+ );
1314
+ segments.push(...nestedResult.segments);
1315
+ matchedIds.push(...nestedResult.matchedIds);
1316
+ }
1317
+
1277
1318
  return { segments, matchedIds };
1278
1319
  }
1279
1320
 
@@ -1296,6 +1337,8 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1296
1337
  pathname: string,
1297
1338
  deps: SegmentResolutionDeps<TEnv>,
1298
1339
  stale?: boolean,
1340
+ /** The matched route's full chain; see ResolveSegmentOptions.chain (fresh.ts). */
1341
+ chain: readonly EntryData[] = entries,
1299
1342
  ): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
1300
1343
  const allSegments: ResolvedSegment[] = [];
1301
1344
  const matchedIds: string[] = [];
@@ -1325,7 +1368,8 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1325
1368
  }
1326
1369
 
1327
1370
  const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
1328
- if (entry.type === "cache") {
1371
+ // Same latch as resolveAllSegments (fresh.ts), route-level cache() included.
1372
+ if (entry.cache) {
1329
1373
  const store = RangoContext.getStore();
1330
1374
  if (store) store.insideCacheScope = true;
1331
1375
  latchCachedHeaderScope("cache", routeKey);
@@ -1348,6 +1392,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1348
1392
  deps,
1349
1393
  actionContext,
1350
1394
  stale,
1395
+ chain,
1351
1396
  ),
1352
1397
  (seg) => ({ segments: [seg], matchedIds: [seg.id] }),
1353
1398
  deps,
@@ -80,6 +80,8 @@ export interface SegmentWrappers<TEnv = any> {
80
80
  interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
81
81
  localRouteName: string,
82
82
  pathname: string,
83
+ stale?: boolean,
84
+ chain?: readonly EntryData[],
83
85
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
84
86
  findInterceptForRoute: (
85
87
  targetRouteKey: string,
@@ -205,6 +207,7 @@ export function createSegmentWrappers<TEnv = any>(
205
207
  localRouteName: string,
206
208
  pathname: string,
207
209
  stale?: boolean,
210
+ chain?: readonly EntryData[],
208
211
  ): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
209
212
  return _resolveAllSegmentsWithRevalidation(
210
213
  entries,
@@ -222,6 +225,7 @@ export function createSegmentWrappers<TEnv = any>(
222
225
  pathname,
223
226
  segmentDeps,
224
227
  stale,
228
+ chain,
225
229
  );
226
230
  }
227
231
 
@@ -13,6 +13,7 @@ import type { RscPayload } from "./types.js";
13
13
  import type { HandlerContext } from "./handler-context.js";
14
14
  import type { RequestContext } from "../server/request-context.js";
15
15
  import type { HandleStore } from "../server/handle-store.js";
16
+ import type { Theme } from "../theme/types.js";
16
17
  import { gateTransitions } from "./transition-gate.js";
17
18
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
18
19
 
@@ -71,7 +72,30 @@ export function buildFullPayload(
71
72
  // entry knows whether to wrap hydration in React.StrictMode. Partial
72
73
  // (navigation) payloads omit it by design; StrictMode is decided once.
73
74
  strictMode: ctx.router.strictMode,
74
- initialTheme: reqCtx.theme,
75
+ initialTheme: payloadInitialTheme(reqCtx),
75
76
  },
76
77
  };
77
78
  }
79
+
80
+ /**
81
+ * The theme a document payload carries as `metadata.initialTheme` (the
82
+ * ThemeProvider's first state), read with no identity guard: it is the
83
+ * router's own read, not handler code (#971).
84
+ *
85
+ * A shell capture carries the no-cookie default, not the capturing visitor's
86
+ * theme. The shell and its ShellCacheEntry.initialTheme serve every visitor,
87
+ * and ThemeProvider re-syncs only from an explicitly stored theme, so a
88
+ * visitor with none kept the capturer's `useTheme().theme` for the page's
89
+ * lifetime. A visitor with a stored theme gets it pre-paint (theme script)
90
+ * and in the provider after mount.
91
+ */
92
+ export function payloadInitialTheme(
93
+ reqCtx: Pick<
94
+ RequestContext<any>,
95
+ "_themeConfig" | "_shellCaptureRun" | "_readTheme"
96
+ >,
97
+ ): Theme | undefined {
98
+ const config = reqCtx._themeConfig;
99
+ if (!config) return undefined;
100
+ return reqCtx._shellCaptureRun ? config.defaultTheme : reqCtx._readTheme();
101
+ }
@@ -31,6 +31,7 @@ import {
31
31
  } from "./helpers.js";
32
32
  import { renderRscFlightStage, renderRscResponse } from "./render-pipeline.js";
33
33
  import { guardOutgoingRedirect } from "./redirect-guard.js";
34
+ import { payloadInitialTheme } from "./full-payload.js";
34
35
  import {
35
36
  resolveSoftRedirectUrl,
36
37
  resolveExternalRedirect,
@@ -85,6 +86,7 @@ import {
85
86
  ORIGIN_CHECK_PHASE_BY_MODE,
86
87
  } from "./origin-guard.js";
87
88
  import { handleRscRendering } from "./rsc-rendering.js";
89
+ import { withoutShellMissMarker } from "./shell-serve.js";
88
90
  import {
89
91
  withTimeout,
90
92
  isTimeoutEnabled,
@@ -436,10 +438,16 @@ export function createRSCHandler<
436
438
  };
437
439
 
438
440
  return async function handler(
439
- request: Request,
441
+ incomingRequest: Request,
440
442
  input: RouterRequestInput<TEnv> = {},
441
443
  ): Promise<Response> {
442
444
  const handlerStart = performance.now();
445
+ // A degraded PPR HIT's reload carries the forced-MISS marker: keep only
446
+ // the flag (set on the request context below) and drop the marker before
447
+ // anything reads the request, so middleware, handlers, loaders, cache
448
+ // keys and the SSR search seed see the URL the visitor asked for.
449
+ const unmarkedRequest = withoutShellMissMarker(incomingRequest);
450
+ const request = unmarkedRequest ?? incomingRequest;
443
451
  // Create the metrics store at handler start so handler:total has startTime=0
444
452
  // and all metrics are relative to the request entry point.
445
453
  const earlyMetricsStore = router.debugPerformance
@@ -566,6 +574,7 @@ export function createRSCHandler<
566
574
  stateCookieName: router.resolvedStateCookieName,
567
575
  version,
568
576
  });
577
+ if (unmarkedRequest) requestContext._shellForcedMiss = true;
569
578
  // Gate on the SAME enabled-semantics withTimeout uses (isTimeoutEnabled):
570
579
  // a `renderStartMs: 0` / negative opt-out disables the timeout, so the
571
580
  // driver's cursor bookkeeping (which only the timeout reads) must be off too.
@@ -662,6 +671,9 @@ export function createRSCHandler<
662
671
  // the host; it never reads or wraps response.body. A downstream throw
663
672
  // skips it entirely (no response exists to hand off).
664
673
  return observePhase(PHASES.response, (responseSpan) => {
674
+ // Header and cookie writes from here on (a streaming loader or
675
+ // render) no longer reach the response.
676
+ requestContext._responseSent = true;
665
677
  let response: Response;
666
678
  if (hasMiddleware) {
667
679
  if (
@@ -1355,7 +1367,7 @@ export function createRSCHandler<
1355
1367
  themeConfig: router.themeConfig,
1356
1368
  warmupEnabled: router.warmupEnabled,
1357
1369
  strictMode: router.strictMode,
1358
- initialTheme: getRequestContext().theme,
1370
+ initialTheme: payloadInitialTheme(getRequestContext()),
1359
1371
  },
1360
1372
  };
1361
1373
 
@@ -15,6 +15,7 @@ import type { MiddlewareFn } from "../router/middleware.js";
15
15
  import { executeMiddleware } from "../router/middleware.js";
16
16
  import { observePhase, PHASES } from "../router/instrument.js";
17
17
  import { gateTransitions } from "./transition-gate.js";
18
+ import { payloadInitialTheme } from "./full-payload.js";
18
19
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
19
20
  import type { RscPayload, ReactFormState } from "./types.js";
20
21
  import {
@@ -444,7 +445,7 @@ function renderPeResponse<TEnv>(
444
445
  themeConfig: ctx.router.themeConfig,
445
446
  warmupEnabled: ctx.router.warmupEnabled,
446
447
  strictMode: ctx.router.strictMode,
447
- initialTheme: getRequestContext().theme,
448
+ initialTheme: payloadInitialTheme(getRequestContext()),
448
449
  },
449
450
  };
450
451
 
@@ -646,7 +647,7 @@ function renderPeErrorResponse<TEnv>(
646
647
  themeConfig: ctx.router.themeConfig,
647
648
  warmupEnabled: ctx.router.warmupEnabled,
648
649
  strictMode: ctx.router.strictMode,
649
- initialTheme: getRequestContext().theme,
650
+ initialTheme: payloadInitialTheme(getRequestContext()),
650
651
  },
651
652
  };
652
653
 
@@ -155,9 +155,12 @@ function createFlightStage<TEnv>(
155
155
  if (input.ctx.devDiscoveryEpoch !== undefined && input.payload.metadata) {
156
156
  input.payload.metadata.devDiscoveryEpoch = input.ctx.devDiscoveryEpoch;
157
157
  }
158
+ // Read once here: onError can fire outside the ALS frame.
159
+ const renderErrors = _getRequestContext()?._renderErrors;
158
160
  const stream = input.ctx.renderToReadableStream<RscPayload>(input.payload, {
159
161
  temporaryReferences: input.temporaryReferences,
160
162
  onError: (error: unknown) => {
163
+ renderErrors?.push(error);
161
164
  input.ctx.callOnError(error, "rendering", {
162
165
  request: input.request,
163
166
  url: input.url,
@@ -105,7 +105,10 @@ export async function serveResponseRouteWithCache(
105
105
  // the host-namespacing and search-normalization rules cannot drift.
106
106
  let cacheKey = `response:${responseType}:${cacheKeyBase(url.host, url.pathname, url.searchParams, undefined, reqCtx._searchParamsFilter)}`;
107
107
 
108
- // Priority 1: route-level key() (full override). Priority 2: store-level
108
+ // Priority 1: route-level key() (full override): the key() results of every
109
+ // cache() enclosing the route, composed like the segment record key
110
+ // (CacheScope.resolveKeyFrom, #970), so a nested cache() never shares an
111
+ // entry across the enclosing partitions. Priority 2: store-level
109
112
  // keyGenerator (modifies the default key).
110
113
  //
111
114
  // A CONFIGURED key()/keyGenerator that THROWS must DEGRADE TO A MISS, not fall
@@ -114,13 +117,13 @@ export async function serveResponseRouteWithCache(
114
117
  // would cache PERSONALIZED output under it and serve it cross-user (cache
115
118
  // poisoning). Mirrors the segment-cache behavior (cache-scope.ts lookupRoute):
116
119
  // a throwing key degrades to a cache miss, never a collision onto the default
117
- // slot. The no-key default path is left untouched (the broad key is correct
118
- // when no key is configured).
120
+ // slot, and so does a key() with no ambient request context to run in
121
+ // (resolveKeyFrom rejects). The no-key default path is left untouched (the
122
+ // broad key is correct when no key is configured).
119
123
  let keyResolutionFailed = false;
120
- if (cacheScope.config !== false && cacheScope.config.key) {
124
+ if (cacheScope.keyFns.length > 0) {
121
125
  try {
122
- const customKey = await cacheScope.config.key(reqCtx);
123
- cacheKey = `response:${customKey}`;
126
+ cacheKey = `response:${await cacheScope.resolveKeyFrom(cacheKey)}`;
124
127
  } catch (error) {
125
128
  keyResolutionFailed = true;
126
129
  reportCacheError(
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { RouterError } from "../errors.js";
10
10
  import { getRequestContext } from "../server/request-context.js";
11
- import { contextGet } from "../context-var.js";
11
+ import { contextGet, isNonCacheable } from "../context-var.js";
12
12
  import { NOCACHE_SYMBOL } from "../cache/taint.js";
13
13
  import { RESPONSE_TYPE_MIME } from "../router/content-negotiation.js";
14
14
  import { createCacheScope, resolveCacheTags } from "../cache/cache-scope.js";
@@ -19,7 +19,10 @@ import {
19
19
  stripInternalParams,
20
20
  } from "../router/handler-context.js";
21
21
  import type { MiddlewareFn } from "../router/middleware.js";
22
- import type { EntryData } from "../server/context.js";
22
+ import {
23
+ assertNonCacheableReadAllowed,
24
+ type EntryData,
25
+ } from "../server/context.js";
23
26
  import type { HandlerContext } from "./handler-context.js";
24
27
  import { createProblemDetails } from "./response-error.js";
25
28
  import {
@@ -85,7 +88,12 @@ export async function handleResponseRoute<TEnv>(
85
88
  originalUrl: reqCtx.originalUrl,
86
89
  pathname: url.pathname,
87
90
  reverse: createReverseFunction(handlerCtx.getRequiredRouteMap()),
88
- get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
91
+ get: ((keyOrVar: any) => {
92
+ if (isNonCacheable(variables, keyOrVar)) {
93
+ assertNonCacheableReadAllowed(keyOrVar);
94
+ }
95
+ return contextGet(variables, keyOrVar);
96
+ }) as any,
89
97
  header: (name: string, value: string) => reqCtx.header(name, value),
90
98
  waitUntil: reqCtx.waitUntil.bind(reqCtx),
91
99
  executionContext: reqCtx.executionContext,