@rangojs/router 0.22.1 → 0.24.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 (259) hide show
  1. package/dist/bin/rango.js +10 -4
  2. package/dist/testing/vitest.js +31 -1
  3. package/dist/types/browser/defer.d.ts +48 -0
  4. package/dist/types/browser/document-revealed.d.ts +17 -0
  5. package/dist/types/browser/event-controller.d.ts +7 -1
  6. package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
  7. package/dist/types/browser/navigation-transaction.d.ts +21 -7
  8. package/dist/types/browser/partial-update.d.ts +39 -8
  9. package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
  10. package/dist/types/browser/react/context.d.ts +3 -2
  11. package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
  12. package/dist/types/browser/response-adapter.d.ts +7 -0
  13. package/dist/types/browser/types.d.ts +24 -10
  14. package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
  15. package/dist/types/build/generate-route-types.d.ts +1 -1
  16. package/dist/types/build/route-types/router-processing.d.ts +1 -1
  17. package/dist/types/build/route-types/scan-filter.d.ts +22 -2
  18. package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
  19. package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
  20. package/dist/types/cache/document-cache-policy.d.ts +24 -0
  21. package/dist/types/cache/document-cache.d.ts +2 -10
  22. package/dist/types/client-urls/client-urls.d.ts +1 -1
  23. package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
  24. package/dist/types/client-urls/navigation.d.ts +0 -37
  25. package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
  26. package/dist/types/client-urls/server-projection.d.ts +29 -1
  27. package/dist/types/client-urls/types.d.ts +34 -69
  28. package/dist/types/client.d.ts +0 -1
  29. package/dist/types/context-var.d.ts +12 -0
  30. package/dist/types/errors.d.ts +15 -0
  31. package/dist/types/handles/deferred-resolution.d.ts +2 -1
  32. package/dist/types/handles/pushed-by.d.ts +31 -0
  33. package/dist/types/index.d.ts +4 -0
  34. package/dist/types/index.rsc.d.ts +2 -0
  35. package/dist/types/loader-store.d.ts +8 -0
  36. package/dist/types/prefetch-markers.d.ts +11 -0
  37. package/dist/types/response-utils.d.ts +7 -0
  38. package/dist/types/reverse.d.ts +8 -8
  39. package/dist/types/route-definition/helpers-types.d.ts +27 -0
  40. package/dist/types/route-definition/rewrite.d.ts +56 -0
  41. package/dist/types/route-map-builder.d.ts +7 -2
  42. package/dist/types/router/basename.d.ts +7 -0
  43. package/dist/types/router/handler-context.d.ts +1 -5
  44. package/dist/types/router/internal-params.d.ts +8 -0
  45. package/dist/types/router/is-action.d.ts +2 -1
  46. package/dist/types/router/middleware-types.d.ts +15 -0
  47. package/dist/types/router/revalidation.d.ts +1 -2
  48. package/dist/types/router/route-trie-builder.d.ts +10 -0
  49. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
  50. package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
  51. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
  52. package/dist/types/rsc/full-payload.d.ts +11 -0
  53. package/dist/types/rsc/handler.d.ts +33 -0
  54. package/dist/types/rsc/helpers.d.ts +8 -0
  55. package/dist/types/rsc/loader-fetch.d.ts +16 -0
  56. package/dist/types/rsc/rewrite-passes.d.ts +74 -0
  57. package/dist/types/rsc/ssr-setup.d.ts +3 -1
  58. package/dist/types/server/context.d.ts +21 -1
  59. package/dist/types/server/handle-store.d.ts +9 -1
  60. package/dist/types/server/request-context.d.ts +52 -2
  61. package/dist/types/server/rewrite-trail.d.ts +49 -0
  62. package/dist/types/server/slot-accessors.d.ts +29 -0
  63. package/dist/types/server.d.ts +1 -0
  64. package/dist/types/testing/flight.entry.d.ts +1 -1
  65. package/dist/types/testing/index.d.ts +4 -4
  66. package/dist/types/testing/internal/action-id.d.ts +10 -1
  67. package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
  68. package/dist/types/testing/internal/test-action.d.ts +8 -0
  69. package/dist/types/testing/render-route.d.ts +8 -0
  70. package/dist/types/testing/run-loader.d.ts +2 -1
  71. package/dist/types/testing/run-middleware.d.ts +20 -0
  72. package/dist/types/testing/run-revalidate.d.ts +74 -0
  73. package/dist/types/testing/run-transition-when.d.ts +2 -3
  74. package/dist/types/testing/serve-shell-request.d.ts +40 -1
  75. package/dist/types/testing/vitest.d.ts +11 -0
  76. package/dist/types/thenable-status.d.ts +6 -0
  77. package/dist/types/types/boundaries.d.ts +9 -0
  78. package/dist/types/types/handler-context.d.ts +14 -0
  79. package/dist/types/types/loader-types.d.ts +30 -0
  80. package/dist/types/types/request-scope.d.ts +7 -0
  81. package/dist/types/types/segments.d.ts +21 -4
  82. package/dist/types/urls/path-helper-types.d.ts +25 -4
  83. package/dist/types/urls/type-extraction.d.ts +6 -2
  84. package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
  85. package/dist/types/vite/discovery/state.d.ts +14 -4
  86. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
  87. package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
  88. package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
  89. package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
  90. package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
  91. package/dist/types/vite/router-discovery.d.ts +10 -0
  92. package/dist/vite/index.js +974 -379
  93. package/package.json +1 -1
  94. package/skills/caching/SKILL.md +54 -5
  95. package/skills/catalog.json +3 -3
  96. package/skills/client-urls/SKILL.md +197 -24
  97. package/skills/cloudflare/SKILL.md +14 -9
  98. package/skills/hooks/SKILL.md +16 -3
  99. package/skills/hooks/data.md +3 -1
  100. package/skills/links/SKILL.md +24 -0
  101. package/skills/loader/SKILL.md +199 -2
  102. package/skills/middleware/SKILL.md +183 -2
  103. package/skills/migrate-nextjs/SKILL.md +2 -2
  104. package/skills/migrate-react-router/SKILL.md +10 -7
  105. package/skills/migrate-react-router/data-and-actions.md +1 -1
  106. package/skills/ppr/SKILL.md +22 -0
  107. package/skills/prerender/SKILL.md +22 -8
  108. package/skills/rango/SKILL.md +13 -7
  109. package/skills/route/SKILL.md +139 -0
  110. package/skills/server-actions/SKILL.md +27 -0
  111. package/skills/testing/SKILL.md +3 -2
  112. package/skills/testing/client-components.md +2 -1
  113. package/skills/testing/middleware.md +31 -10
  114. package/skills/testing/predicates.md +67 -32
  115. package/skills/testing/response-routes.md +1 -0
  116. package/skills/testing/serve-shell-request.md +107 -19
  117. package/skills/testing/setup.md +2 -2
  118. package/skills/typesafety/route-types.md +2 -2
  119. package/skills/use-cache/SKILL.md +4 -2
  120. package/skills/view-transitions/SKILL.md +26 -0
  121. package/src/__augment-tests__/augmented.check.ts +20 -0
  122. package/src/browser/defer.ts +82 -0
  123. package/src/browser/document-revealed.ts +52 -0
  124. package/src/browser/event-controller.ts +46 -32
  125. package/src/browser/merge-segment-loaders.ts +31 -18
  126. package/src/browser/navigation-bridge.ts +82 -16
  127. package/src/browser/navigation-client.ts +48 -36
  128. package/src/browser/navigation-store.ts +13 -0
  129. package/src/browser/navigation-transaction.ts +25 -8
  130. package/src/browser/partial-update.ts +554 -146
  131. package/src/browser/prefetch/fetch.ts +2 -1
  132. package/src/browser/react/Link.tsx +7 -9
  133. package/src/browser/react/NavigationProvider.tsx +88 -49
  134. package/src/browser/react/context.ts +3 -2
  135. package/src/browser/react/filter-segment-order.ts +13 -2
  136. package/src/browser/react/use-router.ts +2 -4
  137. package/src/browser/response-adapter.ts +20 -0
  138. package/src/browser/rsc-router.tsx +17 -45
  139. package/src/browser/server-action-bridge.ts +25 -63
  140. package/src/browser/types.ts +24 -10
  141. package/src/browser/validate-redirect-origin.ts +16 -0
  142. package/src/build/generate-route-types.ts +1 -0
  143. package/src/build/route-types/router-processing.ts +3 -6
  144. package/src/build/route-types/scan-filter.ts +45 -11
  145. package/src/cache/cf/cf-cache-store.ts +59 -21
  146. package/src/cache/cf/cf-cache-types.ts +24 -2
  147. package/src/cache/document-cache-policy.ts +131 -0
  148. package/src/cache/document-cache.ts +21 -107
  149. package/src/client-urls/client-urls.ts +22 -6
  150. package/src/client-urls/hoisted-revalidate.ts +25 -0
  151. package/src/client-urls/navigation.ts +0 -96
  152. package/src/client-urls/revalidate-registry.ts +30 -0
  153. package/src/client-urls/server-projection.ts +151 -65
  154. package/src/client-urls/types.ts +39 -71
  155. package/src/client.tsx +9 -5
  156. package/src/context-var.ts +32 -2
  157. package/src/errors.ts +34 -0
  158. package/src/handles/deferred-resolution.ts +28 -5
  159. package/src/handles/pushed-by.ts +148 -0
  160. package/src/index.rsc.ts +8 -0
  161. package/src/index.ts +7 -0
  162. package/src/loader-store.ts +18 -7
  163. package/src/prefetch-markers.ts +13 -0
  164. package/src/response-utils.ts +19 -0
  165. package/src/reverse.ts +17 -14
  166. package/src/route-content-wrapper.tsx +18 -4
  167. package/src/route-definition/dsl-helpers.ts +27 -4
  168. package/src/route-definition/helpers-types.ts +27 -1
  169. package/src/route-definition/redirect.ts +2 -15
  170. package/src/route-definition/rewrite.ts +146 -0
  171. package/src/route-map-builder.ts +14 -11
  172. package/src/router/basename.ts +20 -0
  173. package/src/router/handler-context.ts +3 -13
  174. package/src/router/intercept-resolution.ts +35 -16
  175. package/src/router/internal-params.ts +16 -0
  176. package/src/router/is-action.ts +2 -1
  177. package/src/router/loader-resolution.ts +85 -4
  178. package/src/router/match-api.ts +36 -1
  179. package/src/router/match-middleware/background-revalidation.ts +16 -4
  180. package/src/router/match-middleware/cache-lookup.ts +35 -7
  181. package/src/router/match-middleware/cache-store.ts +15 -1
  182. package/src/router/match-middleware/intercept-resolution.ts +4 -2
  183. package/src/router/match-result.ts +14 -2
  184. package/src/router/middleware-types.ts +16 -0
  185. package/src/router/middleware.ts +127 -49
  186. package/src/router/navigation-snapshot.ts +2 -1
  187. package/src/router/revalidation.ts +1 -1
  188. package/src/router/route-trie-builder.ts +28 -1
  189. package/src/router/segment-resolution/fresh.ts +22 -18
  190. package/src/router/segment-resolution/loader-cache.ts +2 -1
  191. package/src/router/segment-resolution/loader-mask.ts +5 -3
  192. package/src/router/segment-resolution/loader-snapshot.ts +18 -4
  193. package/src/router/segment-resolution/mask-nested.ts +8 -4
  194. package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
  195. package/src/router/segment-resolution/revalidation.ts +215 -82
  196. package/src/rsc/full-payload.ts +37 -5
  197. package/src/rsc/handler.ts +82 -55
  198. package/src/rsc/helpers.ts +24 -0
  199. package/src/rsc/loader-fetch.ts +79 -24
  200. package/src/rsc/response-cache-serve.ts +18 -2
  201. package/src/rsc/response-route-handler.ts +4 -7
  202. package/src/rsc/rewrite-passes.ts +203 -0
  203. package/src/rsc/rsc-rendering.ts +54 -9
  204. package/src/rsc/shell-capture.ts +2 -1
  205. package/src/rsc/ssr-setup.ts +11 -5
  206. package/src/segment-loader-promise.ts +23 -20
  207. package/src/segment-system.tsx +60 -26
  208. package/src/server/context.ts +39 -5
  209. package/src/server/cookie-parse.ts +36 -12
  210. package/src/server/cookie-store.ts +7 -3
  211. package/src/server/handle-store.ts +152 -25
  212. package/src/server/request-context.ts +175 -64
  213. package/src/server/request-headers.ts +4 -14
  214. package/src/server/rewrite-trail.ts +190 -0
  215. package/src/server/slot-accessors.ts +57 -0
  216. package/src/server.ts +4 -0
  217. package/src/ssr/index.tsx +118 -16
  218. package/src/testing/action-form.ts +2 -11
  219. package/src/testing/dispatch.ts +138 -67
  220. package/src/testing/e2e/page-helpers.ts +3 -1
  221. package/src/testing/flight.entry.ts +1 -0
  222. package/src/testing/index.ts +7 -3
  223. package/src/testing/internal/action-id.ts +16 -1
  224. package/src/testing/internal/client-urls-projection.ts +70 -0
  225. package/src/testing/internal/test-action.ts +25 -0
  226. package/src/testing/render-route.tsx +16 -3
  227. package/src/testing/run-loader.ts +4 -1
  228. package/src/testing/run-middleware.ts +56 -11
  229. package/src/testing/run-revalidate.ts +142 -0
  230. package/src/testing/run-transition-when.ts +7 -8
  231. package/src/testing/serve-shell-request.ts +131 -8
  232. package/src/testing/vitest.ts +67 -1
  233. package/src/thenable-status.ts +11 -0
  234. package/src/types/boundaries.ts +12 -0
  235. package/src/types/handler-context.ts +14 -0
  236. package/src/types/loader-types.ts +30 -0
  237. package/src/types/request-scope.ts +8 -0
  238. package/src/types/segments.ts +26 -4
  239. package/src/urls/path-helper-types.ts +27 -5
  240. package/src/urls/type-extraction.ts +30 -10
  241. package/src/use-loader.tsx +18 -13
  242. package/src/vite/discovery/client-urls-projection.ts +20 -21
  243. package/src/vite/discovery/discover-routers.ts +94 -26
  244. package/src/vite/discovery/state.ts +28 -4
  245. package/src/vite/discovery/virtual-module-codegen.ts +22 -17
  246. package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
  247. package/src/vite/plugins/expose-internal-ids.ts +2 -3
  248. package/src/vite/plugins/hoist/literal.ts +417 -0
  249. package/src/vite/plugins/hoist/scope.ts +370 -0
  250. package/src/vite/plugins/hoist/source.ts +84 -0
  251. package/src/vite/plugins/transition-when-hoist.ts +113 -779
  252. package/src/vite/rango.ts +2 -0
  253. package/src/vite/router-discovery.ts +116 -53
  254. package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
  255. package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
  256. package/dist/types/testing/run-client-revalidate.d.ts +0 -43
  257. package/src/client-urls/revalidate-chain.ts +0 -83
  258. package/src/client-urls/revalidation-protocol.ts +0 -56
  259. package/src/testing/run-client-revalidate.ts +0 -92
@@ -9,11 +9,15 @@
9
9
  import type { ReactNode } from "react";
10
10
  import { invariant } from "../../errors";
11
11
  import { revalidate } from "../loader-resolution.js";
12
- import { evaluateRevalidation } from "../revalidation.js";
12
+ import {
13
+ evaluateRevalidation,
14
+ type EvaluateRevalidationOptions,
15
+ } from "../revalidation.js";
13
16
  import {
14
17
  getParallelEntries,
15
18
  getParallelSlotEntries,
16
19
  type EntryData,
20
+ type LoaderEntry,
17
21
  type ParallelEntryData,
18
22
  } from "../../server/context";
19
23
  import type {
@@ -34,6 +38,13 @@ import {
34
38
  } from "../logging.js";
35
39
  import { resolveLoaderData } from "./loader-cache.js";
36
40
  import { entryLoadingMasksLoaders } from "./loader-mask.js";
41
+ import {
42
+ defersLoader,
43
+ deferUnit,
44
+ fillSends,
45
+ getPrefetchDeferral,
46
+ registerDeferredLoader,
47
+ } from "./prefetch-deferral.js";
37
48
  import {
38
49
  handleHandlerResult,
39
50
  warnOnStreamedResponse,
@@ -102,6 +113,67 @@ function emitRevalidationDecision(
102
113
  });
103
114
  }
104
115
 
116
+ /**
117
+ * Whether a segment the client holds (`held`, the set resolution works on)
118
+ * renders again. A fill keeps it (prefetch-deferral.ts fillSends): no
119
+ * revalidate() predicate runs and nothing is traced or observed. Any other
120
+ * request asks the segment's revalidate() chain, and `onEvaluated` sees that
121
+ * answer.
122
+ */
123
+ async function revalidateHeld<TEnv>({
124
+ held,
125
+ onEvaluated,
126
+ ...options
127
+ }: EvaluateRevalidationOptions<TEnv> & {
128
+ held: ReadonlySet<string>;
129
+ onEvaluated?: (shouldRevalidate: boolean) => void;
130
+ }): Promise<boolean> {
131
+ const fill = fillSends(options.context, options.segment.id, held);
132
+ if (fill !== undefined) return fill;
133
+ const shouldRevalidate = await evaluateRevalidation(options);
134
+ onEvaluated?.(shouldRevalidate);
135
+ return shouldRevalidate;
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // prefetch: false (prefetch-deferral.ts)
140
+ // ---------------------------------------------------------------------------
141
+
142
+ /**
143
+ * How to deliver a loader a prefetch deferred, kept so deliverStartedLoaders
144
+ * can deliver it after all. Keyed by the emitted segment object.
145
+ */
146
+ const deferredLoaderRuns = new WeakMap<ResolvedSegment, () => unknown>();
147
+
148
+ /**
149
+ * A deferred loader something started in this request (a handler that awaits
150
+ * it with ctx.use(), or another loader) ran in the prefetch whatever its flag
151
+ * says: deliver that run instead of having the fill run it a second time. A
152
+ * reader starts it through its registration (prefetch-deferral.ts
153
+ * startDeferredLoader), so the delivery's resolveLoaderData finds the same
154
+ * run or cache() binding. Called once resolution has awaited every handler it
155
+ * awaits; a streamed handler that reads the loader later is not seen, and
156
+ * that loader runs again in the fill.
157
+ */
158
+ function deliverStartedLoaders<TEnv>(
159
+ ctx: HandlerContext<any, TEnv>,
160
+ segments: ResolvedSegment[],
161
+ ): void {
162
+ const plan = getPrefetchDeferral(ctx);
163
+ if (plan?.mode !== "prefetch") return;
164
+ const internal = ctx as InternalHandlerContext<any, TEnv>;
165
+ const started = (loaderId: string): boolean =>
166
+ plan.deferredLoaders?.get(loaderId)?.started === true ||
167
+ internal._loaderStarted?.(loaderId) === true;
168
+ for (const segment of segments) {
169
+ if (!segment.deferred || segment.type !== "loader") continue;
170
+ const run = deferredLoaderRuns.get(segment);
171
+ if (!run || !started(segment.loaderId!)) continue;
172
+ delete segment.deferred;
173
+ segment.loaderData = run();
174
+ }
175
+ }
176
+
105
177
  // ---------------------------------------------------------------------------
106
178
  // Revalidation path (partial match)
107
179
  // ---------------------------------------------------------------------------
@@ -185,7 +257,7 @@ export async function resolveLoadersWithRevalidation<TEnv>(
185
257
  belongsToRoute,
186
258
  };
187
259
 
188
- return await evaluateRevalidation({
260
+ return await revalidateHeld({
189
261
  segment: dummySegment,
190
262
  prevParams,
191
263
  getPrevSegment: null,
@@ -201,6 +273,7 @@ export async function resolveLoadersWithRevalidation<TEnv>(
201
273
  actionContext,
202
274
  stale,
203
275
  traceSource: "loader",
276
+ held: clientSegmentIds,
204
277
  });
205
278
  },
206
279
  async () => true,
@@ -226,35 +299,72 @@ export async function resolveLoadersWithRevalidation<TEnv>(
226
299
  // (matchPartialWithPprReplay) seeds once its doc record hits.
227
300
  const bakeLane = !entryLoadingMasksLoaders(entry.loading);
228
301
 
302
+ const kickoff = (
303
+ loaderEntry: LoaderEntry,
304
+ segmentId: string,
305
+ loaderId: string,
306
+ ): Promise<unknown> =>
307
+ runInsideLoaderScope(
308
+ () =>
309
+ resolveLoaderData(
310
+ loaderEntry,
311
+ ctx,
312
+ ctx.pathname,
313
+ // `bake`, not awaitBeforeFlush: a navigation evaluation never
314
+ // carries awaitBeforeFlush, and an ssr: false loader must pin
315
+ // here exactly as on the document HIT.
316
+ bakeLane || loaderEntry.bake === true ? segmentId : null,
317
+ ),
318
+ loaderId,
319
+ );
320
+ const start = (
321
+ loaderEntry: LoaderEntry,
322
+ segmentId: string,
323
+ loaderId: string,
324
+ ) =>
325
+ deps.wrapLoaderPromise(
326
+ kickoff(loaderEntry, segmentId, loaderId),
327
+ entry,
328
+ segmentId,
329
+ ctx.pathname,
330
+ errorContext,
331
+ );
332
+
229
333
  const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
230
334
  const segments: ResolvedSegment[] = loadersToRun.map(
231
- ({ loaderEntry, loader, segmentId, index }) => ({
232
- id: segmentId,
233
- namespace: entry.id,
234
- type: "loader" as const,
235
- index,
236
- component: null,
237
- params: ctx.params,
238
- loaderId: loader.$$id,
239
- loaderData: deps.wrapLoaderPromise(
240
- runInsideLoaderScope(() =>
241
- resolveLoaderData(
242
- loaderEntry,
243
- ctx,
244
- ctx.pathname,
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,
249
- ),
250
- ),
251
- entry,
252
- segmentId,
253
- ctx.pathname,
254
- errorContext,
255
- ),
256
- belongsToRoute,
257
- }),
335
+ ({ loaderEntry, loader, segmentId, index }) => {
336
+ const base = {
337
+ id: segmentId,
338
+ namespace: entry.id,
339
+ type: "loader" as const,
340
+ index,
341
+ component: null,
342
+ params: ctx.params,
343
+ loaderId: loader.$$id,
344
+ };
345
+ // Only a loader on a segment the client lacks (defersLoader). It
346
+ // would have run. Registered, not started: it runs, and reads its
347
+ // cache(), only if a reader starts it (startDeferredLoader).
348
+ if (defersLoader(ctx, loaderEntry, segmentId)) {
349
+ registerDeferredLoader(ctx, loader.$$id, shortCode, () =>
350
+ kickoff(loaderEntry, segmentId, loader.$$id),
351
+ );
352
+ const deferred: ResolvedSegment = {
353
+ ...base,
354
+ belongsToRoute,
355
+ deferred: true,
356
+ };
357
+ deferredLoaderRuns.set(deferred, () =>
358
+ start(loaderEntry, segmentId, loader.$$id),
359
+ );
360
+ return deferred;
361
+ }
362
+ return {
363
+ ...base,
364
+ loaderData: start(loaderEntry, segmentId, loader.$$id),
365
+ belongsToRoute,
366
+ };
367
+ },
258
368
  );
259
369
 
260
370
  return { segments, matchedIds };
@@ -372,6 +482,7 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
372
482
  await collectEntryLoaders(entry, entry.type === "route");
373
483
  }
374
484
 
485
+ deliverStartedLoaders(context, allLoaderSegments);
375
486
  return { segments: allLoaderSegments, matchedIds: allMatchedIds };
376
487
  }
377
488
 
@@ -612,8 +723,19 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
612
723
  // on the next request, perpetuating the staleness).
613
724
  matchedIds.push(parallelId);
614
725
 
726
+ // A slot below a unit a prefetch skipped goes with it, and no predicate
727
+ // runs: a prefetch sends what the slot declares instead of running its
728
+ // handler.
729
+ let deferred = deferUnit(context, parallelId, "below");
730
+ // A fill sends exactly the slots the client lacks, ahead of every rule
731
+ // below (fillSends).
732
+ const fill = fillSends(context, parallelId, clientSegmentIds);
615
733
  let shouldResolve: boolean;
616
- if (isFullRefetch) {
734
+ if (fill !== undefined) {
735
+ shouldResolve = fill;
736
+ } else if (deferred) {
737
+ shouldResolve = true;
738
+ } else if (isFullRefetch) {
617
739
  // Client has nothing cached — slot MUST render. User revalidate fns are
618
740
  // bypassed here because returning false would leave the segment blank
619
741
  // with no client-side fallback.
@@ -687,18 +809,22 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
687
809
  shouldResolve,
688
810
  );
689
811
 
690
- const { component, handlerRan } = await resolveParallelSlotComponent({
691
- shouldResolve,
692
- parallelEntry,
693
- slot,
694
- parallelId,
695
- handler,
696
- context,
697
- deps,
698
- routeKey,
699
- params,
700
- boundaryEntry: options?.boundaryEntry ?? entry,
701
- });
812
+ // A slot with its own flagged loading() is its own unit.
813
+ if (shouldResolve) deferred ??= deferUnit(context, parallelId, "slot");
814
+ const { component, handlerRan } = deferred
815
+ ? { component: null, handlerRan: false }
816
+ : await resolveParallelSlotComponent({
817
+ shouldResolve,
818
+ parallelEntry,
819
+ slot,
820
+ parallelId,
821
+ handler,
822
+ context,
823
+ deps,
824
+ routeKey,
825
+ params,
826
+ boundaryEntry: options?.boundaryEntry ?? entry,
827
+ });
702
828
 
703
829
  segments.push({
704
830
  id: parallelId,
@@ -719,6 +845,7 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
719
845
  ...(parallelEntry.mountPath
720
846
  ? { mountPath: parallelEntry.mountPath }
721
847
  : {}),
848
+ ...deferred,
722
849
  });
723
850
 
724
851
  if (loaderOrder === "after") {
@@ -750,6 +877,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
750
877
  const matchedId = entry.shortCode;
751
878
 
752
879
  let handlerRan = false;
880
+ let deferred: { readonly deferred: true } | undefined;
753
881
  const component = await revalidate(
754
882
  async () => {
755
883
  const hasSegment = clientSegmentIds.has(entry.shortCode);
@@ -795,7 +923,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
795
923
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
796
924
  };
797
925
 
798
- const shouldRevalidate = await evaluateRevalidation({
926
+ return revalidateHeld({
799
927
  segment: dummySegment,
800
928
  prevParams,
801
929
  getPrevSegment: null,
@@ -812,20 +940,26 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
812
940
  stale,
813
941
  traceSource:
814
942
  entry.type === "route" ? "route-handler" : "layout-handler",
943
+ held: clientSegmentIds,
944
+ onEvaluated: (shouldRevalidate) => {
945
+ emitRevalidationDecision(
946
+ entry.shortCode,
947
+ context.pathname,
948
+ routeKey,
949
+ shouldRevalidate,
950
+ );
951
+ debugLog("segment.revalidate", "entry revalidation decision", {
952
+ segmentId: entry.shortCode,
953
+ shouldRevalidate,
954
+ });
955
+ },
815
956
  });
816
- emitRevalidationDecision(
817
- entry.shortCode,
818
- context.pathname,
819
- routeKey,
820
- shouldRevalidate,
821
- );
822
- debugLog("segment.revalidate", "entry revalidation decision", {
823
- segmentId: entry.shortCode,
824
- shouldRevalidate,
825
- });
826
- return shouldRevalidate;
827
957
  },
828
958
  async () => {
959
+ // Only an entry the client lacks (defersUnit). Its handler would
960
+ // have run, and a prefetch sends the entry's fallback in its place.
961
+ deferred = deferUnit(context, entry.shortCode, "chain");
962
+ if (deferred) return null;
829
963
  handlerRan = true;
830
964
  const doneHandler = track(`handler:${entry.id}`, 2);
831
965
  (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
@@ -928,6 +1062,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
928
1062
  : {}),
929
1063
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
930
1064
  _handlerRan: handlerRan,
1065
+ ...deferred,
931
1066
  };
932
1067
 
933
1068
  return { segment, matchedId };
@@ -1020,10 +1155,8 @@ export async function resolveSegmentWithRevalidation<TEnv>(
1020
1155
  }
1021
1156
  }
1022
1157
 
1023
- if (routeHandlerResult) {
1024
- // Route entry: handler already executed above; resolve parallels
1025
- // (handler data visible) then push handler segment last for tree order.
1026
- const parallelResult = await resolveParallelSegmentsWithRevalidation(
1158
+ const resolveSlots = () =>
1159
+ resolveParallelSegmentsWithRevalidation(
1027
1160
  entry,
1028
1161
  params,
1029
1162
  context,
@@ -1038,6 +1171,11 @@ export async function resolveSegmentWithRevalidation<TEnv>(
1038
1171
  actionContext,
1039
1172
  stale,
1040
1173
  );
1174
+
1175
+ if (routeHandlerResult) {
1176
+ // Route entry: handler already executed above; resolve parallels
1177
+ // (handler data visible) then push handler segment last for tree order.
1178
+ const parallelResult = await resolveSlots();
1041
1179
  segments.push(...parallelResult.segments);
1042
1180
  matchedIds.push(...parallelResult.matchedIds);
1043
1181
 
@@ -1064,21 +1202,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
1064
1202
  segments.push(handlerResult.segment);
1065
1203
  matchedIds.push(handlerResult.matchedId);
1066
1204
 
1067
- const parallelResult = await resolveParallelSegmentsWithRevalidation(
1068
- entry,
1069
- params,
1070
- context,
1071
- belongsToRoute,
1072
- clientSegmentIds,
1073
- prevParams,
1074
- request,
1075
- prevUrl,
1076
- nextUrl,
1077
- routeKey,
1078
- deps,
1079
- actionContext,
1080
- stale,
1081
- );
1205
+ const parallelResult = await resolveSlots();
1082
1206
  segments.push(...parallelResult.segments);
1083
1207
  matchedIds.push(...parallelResult.matchedIds);
1084
1208
 
@@ -1192,6 +1316,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1192
1316
  // so ctx.set() values are visible to parallel children.
1193
1317
  matchedIds.push(orphan.shortCode);
1194
1318
 
1319
+ let deferred: { readonly deferred: true } | undefined;
1195
1320
  const component = await revalidate(
1196
1321
  async () => {
1197
1322
  if (!clientSegmentIds.has(orphan.shortCode)) {
@@ -1221,7 +1346,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1221
1346
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
1222
1347
  };
1223
1348
 
1224
- const shouldRevalidate = await evaluateRevalidation({
1349
+ return revalidateHeld({
1225
1350
  segment: dummySegment,
1226
1351
  prevParams,
1227
1352
  getPrevSegment: null,
@@ -1237,16 +1362,22 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1237
1362
  actionContext,
1238
1363
  stale,
1239
1364
  traceSource: "orphan-layout",
1365
+ held: clientSegmentIds,
1366
+ onEvaluated: (shouldRevalidate) =>
1367
+ emitRevalidationDecision(
1368
+ orphan.shortCode,
1369
+ context.pathname,
1370
+ routeKey,
1371
+ shouldRevalidate,
1372
+ ),
1240
1373
  });
1241
- emitRevalidationDecision(
1242
- orphan.shortCode,
1243
- context.pathname,
1244
- routeKey,
1245
- shouldRevalidate,
1246
- );
1247
- return shouldRevalidate;
1248
1374
  },
1249
- async () => resolveLayoutComponent(orphan, context),
1375
+ async () => {
1376
+ // Below a unit a prefetch skipped: the orphan goes with it.
1377
+ deferred = deferUnit(context, orphan.shortCode, "below");
1378
+ if (deferred) return null;
1379
+ return resolveLayoutComponent(orphan, context);
1380
+ },
1250
1381
  () => null,
1251
1382
  );
1252
1383
 
@@ -1265,6 +1396,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1265
1396
  deps.viewTransitionDefault,
1266
1397
  ),
1267
1398
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
1399
+ ...deferred,
1268
1400
  });
1269
1401
 
1270
1402
  // Resolve the orphan layout's parallel slots through the shared main-path
@@ -1425,5 +1557,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1425
1557
  }
1426
1558
  }
1427
1559
 
1560
+ deliverStartedLoaders(context, allSegments);
1428
1561
  return { segments: allSegments, matchedIds };
1429
1562
  }
@@ -14,7 +14,11 @@ 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
16
  import type { Theme } from "../theme/types.js";
17
- import { documentCacheStoresRender } from "../cache/document-cache.js";
17
+ import {
18
+ rendersForSharedAudience,
19
+ rewriteTrail,
20
+ visitorUrl,
21
+ } from "../server/rewrite-trail.js";
18
22
  import { attachTransitionWhen } from "./attach-transition-when.js";
19
23
  import { publicRouteName } from "../route-name.js";
20
24
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
@@ -38,7 +42,7 @@ export function buildFullPayload(
38
42
  ): RscPayload {
39
43
  return {
40
44
  metadata: {
41
- pathname: url.pathname,
45
+ pathname: renderedLocation(reqCtx, url).pathname,
42
46
  routerId: ctx.router.id,
43
47
  basename: ctx.router.basename,
44
48
  segments: attachTransitionWhen(m.segments, reqCtx),
@@ -84,6 +88,36 @@ export function buildFullPayload(
84
88
  };
85
89
  }
86
90
 
91
+ /**
92
+ * The URL a render shows as the page's location (payload `pathname`, the SSR
93
+ * search and origin seed). For a request middleware rewrote, the visitor's
94
+ * URL, so the server markup and the hydrating client (which reads the address
95
+ * bar) agree on `usePathname()` and `useSearchParams()`. A render other
96
+ * visitors may be served shows `url`, the target, as a direct visit does: a
97
+ * shell capture, and a render the document cache stores (its key is the
98
+ * target). A render that shows the visitor's URL marks
99
+ * `_payloadVisitorLocation`, and the document cache does not store it.
100
+ */
101
+ export function renderedLocation(
102
+ reqCtx:
103
+ | Pick<
104
+ RequestContext<any>,
105
+ | "_variables"
106
+ | "_shellCaptureRun"
107
+ | "_documentCacheRender"
108
+ | "_payloadVisitorLocation"
109
+ | "res"
110
+ >
111
+ | undefined,
112
+ url: URL,
113
+ ): URL {
114
+ if (!reqCtx || !rewriteTrail(reqCtx) || rendersForSharedAudience(reqCtx)) {
115
+ return url;
116
+ }
117
+ reqCtx._payloadVisitorLocation = true;
118
+ return visitorUrl(reqCtx, url);
119
+ }
120
+
87
121
  /**
88
122
  * The theme a document payload carries as `metadata.initialTheme` (the
89
123
  * ThemeProvider's first state), read with no identity guard: it is the
@@ -116,9 +150,7 @@ export function payloadInitialTheme(
116
150
  ): Theme | undefined {
117
151
  const config = reqCtx._themeConfig;
118
152
  if (!config) return undefined;
119
- if (reqCtx._shellCaptureRun || documentCacheStoresRender(reqCtx)) {
120
- return config.defaultTheme;
121
- }
153
+ if (rendersForSharedAudience(reqCtx)) return config.defaultTheme;
122
154
  const theme = reqCtx._readTheme();
123
155
  if (theme !== config.defaultTheme) reqCtx._payloadVisitorTheme = true;
124
156
  return theme;