@rangojs/router 0.18.0 → 0.19.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 (244) hide show
  1. package/dist/types/browser/event-controller.d.ts +3 -3
  2. package/dist/types/browser/history-state.d.ts +2 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  4. package/dist/types/browser/partial-update.d.ts +16 -1
  5. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  6. package/dist/types/browser/react/Link.d.ts +19 -9
  7. package/dist/types/browser/react/context.d.ts +2 -2
  8. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  9. package/dist/types/browser/react/location-state.d.ts +1 -1
  10. package/dist/types/browser/transition-when.d.ts +80 -0
  11. package/dist/types/browser/types.d.ts +104 -9
  12. package/dist/types/build/generate-manifest.d.ts +8 -0
  13. package/dist/types/cache/background-task.d.ts +24 -2
  14. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +44 -13
  16. package/dist/types/cache/cache-policy.d.ts +1 -1
  17. package/dist/types/cache/cache-runtime.d.ts +7 -0
  18. package/dist/types/cache/cache-scope.d.ts +117 -35
  19. package/dist/types/cache/cache-tag.d.ts +24 -4
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
  21. package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
  22. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
  23. package/dist/types/cache/document-cache.d.ts +10 -0
  24. package/dist/types/cache/handle-snapshot.d.ts +40 -8
  25. package/dist/types/cache/invalidation-order.d.ts +23 -0
  26. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +88 -141
  28. package/dist/types/cache/tag-invalidation.d.ts +38 -14
  29. package/dist/types/cache/types.d.ts +68 -61
  30. package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
  31. package/dist/types/client-urls/navigation.d.ts +28 -2
  32. package/dist/types/client-urls/server-projection.d.ts +5 -2
  33. package/dist/types/client-urls/types.d.ts +5 -5
  34. package/dist/types/client.d.ts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  38. package/dist/types/route-name.d.ts +7 -0
  39. package/dist/types/router/error-handling.d.ts +1 -1
  40. package/dist/types/router/intercept-resolution.d.ts +2 -0
  41. package/dist/types/router/match-context.d.ts +5 -0
  42. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  43. package/dist/types/router/router-options.d.ts +4 -1
  44. package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
  45. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  46. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
  47. package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
  48. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  49. package/dist/types/router/transition-when.d.ts +24 -10
  50. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  51. package/dist/types/rsc/full-payload.d.ts +14 -7
  52. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  53. package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
  54. package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
  55. package/dist/types/rsc/shell-capture.d.ts +78 -81
  56. package/dist/types/rsc/shell-serve.d.ts +19 -48
  57. package/dist/types/rsc/types.d.ts +2 -0
  58. package/dist/types/segment-system.d.ts +7 -0
  59. package/dist/types/server/context.d.ts +89 -61
  60. package/dist/types/server/cookie-store.d.ts +30 -18
  61. package/dist/types/server/handle-store.d.ts +52 -1
  62. package/dist/types/server/request-context.d.ts +66 -45
  63. package/dist/types/server/request-headers.d.ts +44 -0
  64. package/dist/types/server.d.ts +1 -0
  65. package/dist/types/testing/e2e/index.d.ts +1 -1
  66. package/dist/types/testing/index.d.ts +3 -2
  67. package/dist/types/testing/internal/action-id.d.ts +7 -0
  68. package/dist/types/testing/location-state-key.d.ts +24 -0
  69. package/dist/types/testing/render-route.d.ts +17 -1
  70. package/dist/types/testing/run-transition-when.d.ts +44 -69
  71. package/dist/types/testing/serve-shell-request.d.ts +7 -6
  72. package/dist/types/testing/shell-status.d.ts +34 -22
  73. package/dist/types/transition-when-ref.d.ts +79 -0
  74. package/dist/types/types/cache-types.d.ts +15 -3
  75. package/dist/types/types/index.d.ts +1 -1
  76. package/dist/types/types/segments.d.ts +86 -71
  77. package/dist/types/urls/path-helper-types.d.ts +3 -4
  78. package/dist/types/urls/pattern-types.d.ts +3 -4
  79. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  80. package/dist/vite/index.js +960 -97
  81. package/package.json +1 -1
  82. package/skills/cache-guide/SKILL.md +76 -49
  83. package/skills/caching/SKILL.md +167 -69
  84. package/skills/client-urls/SKILL.md +1 -1
  85. package/skills/comparison/references/framework-comparison.md +8 -5
  86. package/skills/document-cache/SKILL.md +24 -0
  87. package/skills/hooks/SKILL.md +3 -1
  88. package/skills/hooks/navigation.md +23 -2
  89. package/skills/hooks/state.md +98 -6
  90. package/skills/intercept/SKILL.md +29 -12
  91. package/skills/loader/SKILL.md +52 -21
  92. package/skills/middleware/SKILL.md +18 -0
  93. package/skills/migrate-nextjs/SKILL.md +3 -3
  94. package/skills/observability/SKILL.md +6 -5
  95. package/skills/parallel/SKILL.md +1 -1
  96. package/skills/ppr/SKILL.md +129 -77
  97. package/skills/prerender/SKILL.md +3 -3
  98. package/skills/rango/SKILL.md +25 -6
  99. package/skills/route/SKILL.md +2 -0
  100. package/skills/testing/SKILL.md +27 -25
  101. package/skills/testing/cache-prerender.md +9 -9
  102. package/skills/testing/client-components.md +45 -3
  103. package/skills/testing/loader.md +3 -2
  104. package/skills/testing/middleware.md +1 -0
  105. package/skills/testing/predicates.md +49 -49
  106. package/skills/testing/render-handler.md +1 -0
  107. package/skills/testing/serve-shell-request.md +14 -0
  108. package/skills/testing/server-actions.md +9 -3
  109. package/skills/theme/SKILL.md +8 -4
  110. package/skills/typesafety/env-and-bindings.md +8 -0
  111. package/skills/use-cache/SKILL.md +64 -19
  112. package/skills/view-transitions/SKILL.md +150 -29
  113. package/src/__augment-tests__/augmented.check.ts +23 -0
  114. package/src/browser/event-controller.ts +4 -4
  115. package/src/browser/history-state.ts +52 -1
  116. package/src/browser/link-interceptor.ts +4 -0
  117. package/src/browser/navigation-bridge.ts +77 -18
  118. package/src/browser/navigation-client.ts +4 -3
  119. package/src/browser/navigation-store.ts +43 -1
  120. package/src/browser/navigation-transaction.ts +31 -11
  121. package/src/browser/partial-update.ts +146 -35
  122. package/src/browser/prefetch/cache.ts +6 -6
  123. package/src/browser/prefetch/fetch.ts +2 -2
  124. package/src/browser/react/Link.tsx +38 -33
  125. package/src/browser/react/NavigationProvider.tsx +2 -2
  126. package/src/browser/react/context.ts +2 -2
  127. package/src/browser/react/location-state-shared.ts +241 -49
  128. package/src/browser/react/location-state.ts +5 -2
  129. package/src/browser/rsc-router.tsx +4 -0
  130. package/src/browser/server-action-bridge.ts +84 -15
  131. package/src/browser/transition-when.ts +205 -0
  132. package/src/browser/types.ts +124 -10
  133. package/src/build/generate-manifest.ts +77 -1
  134. package/src/cache/background-task.ts +55 -2
  135. package/src/cache/cache-exec-scope.ts +48 -1
  136. package/src/cache/cache-key-utils.ts +56 -16
  137. package/src/cache/cache-policy.ts +9 -4
  138. package/src/cache/cache-runtime.ts +197 -144
  139. package/src/cache/cache-scope.ts +304 -112
  140. package/src/cache/cache-tag.ts +105 -13
  141. package/src/cache/cf/cf-cache-store.ts +152 -76
  142. package/src/cache/cf/cf-shell-frame.ts +2 -2
  143. package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
  144. package/src/cache/document-cache.ts +53 -7
  145. package/src/cache/handle-snapshot.ts +52 -9
  146. package/src/cache/invalidation-order.ts +84 -0
  147. package/src/cache/request-tag-mask.ts +147 -0
  148. package/src/cache/shell-snapshot.ts +130 -348
  149. package/src/cache/tag-invalidation.ts +57 -51
  150. package/src/cache/types.ts +71 -67
  151. package/src/cache/vercel/vercel-cache-store.ts +61 -58
  152. package/src/client-urls/client-root.tsx +8 -4
  153. package/src/client-urls/client-urls.ts +8 -6
  154. package/src/client-urls/navigation.ts +55 -4
  155. package/src/client-urls/server-projection.ts +31 -10
  156. package/src/client-urls/types.ts +12 -5
  157. package/src/client.tsx +2 -0
  158. package/src/host/cookie-handler.ts +2 -1
  159. package/src/index.rsc.ts +3 -0
  160. package/src/index.ts +3 -0
  161. package/src/loader-redirect.tsx +7 -3
  162. package/src/prerender/build-shell-capture.ts +155 -146
  163. package/src/route-definition/dsl-helpers.ts +2 -0
  164. package/src/route-definition/helpers-types.ts +11 -11
  165. package/src/route-name.ts +12 -0
  166. package/src/router/content-negotiation.ts +4 -1
  167. package/src/router/error-handling.ts +5 -2
  168. package/src/router/handler-context.ts +10 -7
  169. package/src/router/intercept-resolution.ts +11 -1
  170. package/src/router/loader-resolution.ts +28 -3
  171. package/src/router/logging.ts +5 -3
  172. package/src/router/match-api.ts +62 -34
  173. package/src/router/match-context.ts +5 -0
  174. package/src/router/match-handlers.ts +9 -29
  175. package/src/router/match-middleware/background-revalidation.ts +3 -5
  176. package/src/router/match-middleware/cache-lookup.ts +35 -19
  177. package/src/router/match-middleware/cache-store.ts +15 -9
  178. package/src/router/match-result.ts +1 -0
  179. package/src/router/middleware.ts +2 -5
  180. package/src/router/navigation-snapshot.ts +6 -5
  181. package/src/router/prerender-match.ts +4 -0
  182. package/src/router/request-classification.ts +7 -4
  183. package/src/router/router-options.ts +4 -1
  184. package/src/router/segment-resolution/fresh.ts +0 -4
  185. package/src/router/segment-resolution/loader-cache.ts +150 -25
  186. package/src/router/segment-resolution/loader-mask.ts +3 -4
  187. package/src/router/segment-resolution/loader-snapshot.ts +15 -78
  188. package/src/router/segment-resolution/mask-nested.ts +47 -22
  189. package/src/router/segment-resolution/revalidation.ts +0 -3
  190. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  191. package/src/router/telemetry.ts +6 -3
  192. package/src/router/transition-when.ts +34 -52
  193. package/src/router.ts +2 -1
  194. package/src/rsc/attach-transition-when.ts +47 -0
  195. package/src/rsc/full-payload.ts +30 -10
  196. package/src/rsc/handler.ts +12 -10
  197. package/src/rsc/loader-fetch.ts +2 -1
  198. package/src/rsc/manifest-init.ts +4 -0
  199. package/src/rsc/origin-guard.ts +8 -5
  200. package/src/rsc/progressive-enhancement.ts +7 -30
  201. package/src/rsc/response-cache-serve.ts +68 -80
  202. package/src/rsc/response-route-handler.ts +2 -2
  203. package/src/rsc/rsc-rendering.ts +206 -252
  204. package/src/rsc/server-action.ts +8 -20
  205. package/src/rsc/shell-build-manifest.ts +22 -20
  206. package/src/rsc/shell-capture-constants.ts +136 -20
  207. package/src/rsc/shell-capture.ts +352 -589
  208. package/src/rsc/shell-serve.ts +73 -109
  209. package/src/rsc/ssr-setup.ts +3 -2
  210. package/src/rsc/types.ts +2 -0
  211. package/src/segment-system.tsx +40 -7
  212. package/src/server/context.ts +189 -124
  213. package/src/server/cookie-store.ts +153 -127
  214. package/src/server/handle-store.ts +245 -27
  215. package/src/server/request-context.ts +123 -84
  216. package/src/server/request-headers.ts +134 -0
  217. package/src/server.ts +4 -0
  218. package/src/testing/dispatch.ts +4 -7
  219. package/src/testing/e2e/index.ts +1 -0
  220. package/src/testing/index.ts +5 -0
  221. package/src/testing/internal/action-id.ts +25 -0
  222. package/src/testing/internal/context.ts +1 -1
  223. package/src/testing/location-state-key.ts +45 -0
  224. package/src/testing/render-handler.ts +1 -1
  225. package/src/testing/render-route.tsx +109 -16
  226. package/src/testing/run-client-revalidate.ts +3 -19
  227. package/src/testing/run-loader.ts +5 -1
  228. package/src/testing/run-transition-when.ts +109 -172
  229. package/src/testing/serve-shell-request.ts +10 -7
  230. package/src/testing/shell-status.ts +60 -56
  231. package/src/transition-when-ref.ts +220 -0
  232. package/src/types/cache-types.ts +15 -3
  233. package/src/types/index.ts +3 -0
  234. package/src/types/segments.ts +94 -91
  235. package/src/urls/path-helper-types.ts +3 -4
  236. package/src/urls/pattern-types.ts +3 -4
  237. package/src/vite/discovery/discover-routers.ts +3 -0
  238. package/src/vite/discovery/shell-prerender-phase.ts +0 -2
  239. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  240. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  241. package/src/vite/rango.ts +2 -0
  242. package/src/vite/router-discovery.ts +75 -5
  243. package/dist/types/rsc/transition-gate.d.ts +0 -23
  244. package/src/rsc/transition-gate.ts +0 -86
@@ -1,76 +1,58 @@
1
1
  import { getParallelSlotEntries, type EntryData } from "../server/context.js";
2
2
  import type { RequestContext } from "../server/request-context.js";
3
- import type { TransitionWhenContext } from "../types/segments.js";
3
+ import type { TransitionWhenSite } from "../transition-when-ref.js";
4
+ import type { TransitionWhenFn } from "../types/segments.js";
4
5
 
5
- export type TransitionWhenErrorReporter = (
6
- error: unknown,
7
- segmentId: string,
8
- ) => void;
9
-
10
- export function createTransitionWhenContext<TEnv>(
11
- ctx: RequestContext<TEnv>,
12
- target?: {
13
- params: Record<string, string>;
14
- routeName?: string;
15
- },
16
- ): TransitionWhenContext<Record<string, string>, TEnv> {
17
- return {
18
- currentUrl: ctx._gateCurrentUrl,
19
- currentParams: ctx._gateCurrentParams,
20
- fromRouteName: ctx._prevRouteKey as TransitionWhenContext["fromRouteName"],
21
- nextUrl: ctx.url,
22
- nextParams: target?.params ?? ctx.params,
23
- toRouteName: (target?.routeName ??
24
- ctx.routeName) as TransitionWhenContext["toRouteName"],
25
- actionId: ctx._gateActionId,
26
- actionUrl: ctx._gateActionUrl,
27
- actionResult: ctx._gateActionResult,
28
- formData: ctx._gateFormData,
29
- method: ctx.request.method,
30
- get: ctx.get,
31
- env: ctx.env,
32
- };
6
+ /** A matched entry's transition({ when }) and where it was declared. */
7
+ export interface TransitionWhenRecord {
8
+ readonly when: TransitionWhenFn;
9
+ readonly site: TransitionWhenSite;
33
10
  }
34
11
 
35
- /** Evaluate server-only transition gates before handlers on a PPR route. */
36
- export function evaluatePprTransitionWhen<TEnv>(
37
- entries: EntryData[],
12
+ /**
13
+ * Record the transition({ when }) references of a matched entry chain, keyed
14
+ * by the segment id each entry resolves to. The server never calls them:
15
+ * rsc/attach-transition-when.ts attaches them to the payload's segments right
16
+ * before Flight, and the browser decides (browser/transition-when.ts).
17
+ *
18
+ * Recorded from the static route definition on EVERY match (fresh, cache hit,
19
+ * prerender, PPR replay) because stored segments never carry them:
20
+ * applyViewTransitionDefault strips `when` at resolution, since segment stores
21
+ * JSON-serialize the transition config (dropping a function on some stores,
22
+ * keeping it by reference on others). Segment ids follow resolution: an
23
+ * entry's shortCode, and `${owner.shortCode}.${slot}` for a rendered parallel
24
+ * slot.
25
+ */
26
+ export function recordTransitionWhenRefs<TEnv>(
27
+ entries: readonly EntryData[] | undefined,
38
28
  ctx: RequestContext<TEnv>,
39
- target: {
40
- params: Record<string, string>;
41
- routeName?: string;
42
- },
43
- reportError: TransitionWhenErrorReporter,
29
+ route: { routeName?: string; pattern?: string } = {},
44
30
  ): void {
45
- const decisions = new Map<string, boolean>();
46
- const whenContext = createTransitionWhenContext(ctx, target);
31
+ const refs = new Map<string, TransitionWhenRecord>();
47
32
  const visited = new Set<EntryData>();
48
33
 
49
- const evaluate = (entry: EntryData, segmentId: string): void => {
34
+ const record = (entry: EntryData, segmentId: string): void => {
50
35
  const when = entry.transition?.when;
51
- if (!when) return;
52
- try {
53
- decisions.set(segmentId, when(whenContext) !== false);
54
- } catch (error) {
55
- decisions.set(segmentId, false);
56
- reportError(error, segmentId);
57
- }
36
+ if (when === undefined) return;
37
+ refs.set(segmentId, {
38
+ when,
39
+ site: { ...route, entryType: entry.type },
40
+ });
58
41
  };
59
42
 
60
43
  const visitEntryAndOrphanLayouts = (entry: EntryData): void => {
61
44
  if (visited.has(entry)) return;
62
45
  visited.add(entry);
63
- evaluate(entry, entry.shortCode);
46
+ record(entry, entry.shortCode);
64
47
 
65
48
  for (const orphan of entry.layout) visitEntryAndOrphanLayouts(orphan);
66
- // Rendered parallel slots are leaves identified by their owning segment.
67
49
  for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
68
50
  entry.parallel,
69
51
  )) {
70
- evaluate(parallelEntry, `${entry.shortCode}.${slot}`);
52
+ record(parallelEntry, `${entry.shortCode}.${slot}`);
71
53
  }
72
54
  };
73
55
 
74
- for (const entry of entries) visitEntryAndOrphanLayouts(entry);
75
- ctx._pprTransitionDecisions = decisions.size > 0 ? decisions : undefined;
56
+ for (const entry of entries ?? []) visitEntryAndOrphanLayouts(entry);
57
+ ctx._transitionWhenRefs = refs.size > 0 ? refs : undefined;
76
58
  }
package/src/router.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { requestHeaders } from "./server/request-headers.js";
1
2
  import { type ReactNode } from "react";
2
3
  import { createCacheScope } from "./cache/cache-scope.js";
3
4
  import { resolveCacheProfiles } from "./cache/profile-registry.js";
@@ -1161,7 +1162,7 @@ export function createRouter<TEnv = any>(
1161
1162
  | null = null;
1162
1163
 
1163
1164
  return async (request: Request, input: RouterRequestInput<TEnv> = {}) => {
1164
- const requestedDiscoveryEpoch = request.headers.get(
1165
+ const requestedDiscoveryEpoch = requestHeaders(request).get(
1165
1166
  DEV_DISCOVERY_PROBE_HEADER,
1166
1167
  );
1167
1168
  if (
@@ -0,0 +1,47 @@
1
+ import type { MatchResult } from "../types.js";
2
+ import type { getRequestContext } from "../server/request-context.js";
3
+ import {
4
+ createTransitionWhenError,
5
+ isTransitionWhenValidationEnabled,
6
+ transitionWhenProblem,
7
+ } from "../transition-when-ref.js";
8
+
9
+ /**
10
+ * Attach transition({ when }) references to a payload's segments, right before
11
+ * Flight. Returns a copy for every segment it changes: the input segments may
12
+ * be the records a cache, prerender, or shell snapshot holds.
13
+ *
14
+ * Only a segment that carries `transition` gets its predicate. The config
15
+ * itself never varies per request, so the segment key and the <ViewTransition>
16
+ * wrapper depend only on the route definition (#995); the browser decides for
17
+ * every committed segment, kept ones included (#989).
18
+ *
19
+ * Render-time backstop for an invalid `when` that route discovery did not see
20
+ * (a route evaluated lazily at runtime): it throws the discovery error. A
21
+ * plain function reaching Flight would otherwise fail with React's generic
22
+ * "Functions cannot be passed directly to Client Components", or be dropped.
23
+ */
24
+ export function attachTransitionWhen(
25
+ segments: MatchResult["segments"],
26
+ ctx: ReturnType<typeof getRequestContext>,
27
+ ): MatchResult["segments"] {
28
+ const refs = ctx?._transitionWhenRefs;
29
+ if (!refs) return segments;
30
+ const strict = isTransitionWhenValidationEnabled();
31
+ let out = segments;
32
+ for (let i = 0; i < segments.length; i++) {
33
+ const segment = segments[i];
34
+ const record = segment.transition ? refs.get(segment.id) : undefined;
35
+ if (!record || segment.transition!.when === record.when) continue;
36
+ const problem = transitionWhenProblem(record.when, strict);
37
+ if (problem) {
38
+ throw createTransitionWhenError(record.when, problem, record.site);
39
+ }
40
+ if (out === segments) out = [...segments];
41
+ out[i] = {
42
+ ...segment,
43
+ transition: { ...segment.transition, when: record.when },
44
+ };
45
+ }
46
+ return out;
47
+ }
@@ -14,7 +14,9 @@ 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 { gateTransitions } from "./transition-gate.js";
17
+ import { documentCacheStoresRender } from "../cache/document-cache.js";
18
+ import { attachTransitionWhen } from "./attach-transition-when.js";
19
+ import { publicRouteName } from "../route-name.js";
18
20
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
19
21
 
20
22
  /**
@@ -39,11 +41,12 @@ export function buildFullPayload(
39
41
  pathname: url.pathname,
40
42
  routerId: ctx.router.id,
41
43
  basename: ctx.router.basename,
42
- segments: gateTransitions(m.segments, reqCtx, ctx.router.onError),
44
+ segments: attachTransitionWhen(m.segments, reqCtx),
43
45
  matched: m.matched,
44
46
  diff: m.diff,
45
47
  resolvedIds: m.resolvedIds,
46
48
  params: m.params,
49
+ routeName: publicRouteName(m.routeName),
47
50
  isPartial: false,
48
51
  interceptTargets: m.interceptTargets,
49
52
  rootLayout: ctx.router.rootLayout,
@@ -82,20 +85,37 @@ export function buildFullPayload(
82
85
  * ThemeProvider's first state), read with no identity guard: it is the
83
86
  * router's own read, not handler code (#971).
84
87
  *
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.
88
+ * A render other visitors may be served carries the no-cookie default, not
89
+ * the rendering visitor's theme: a shell capture, and a render the document
90
+ * cache runs whose response opted in before next() (documentCacheStoresRender,
91
+ * #978). The shell, its ShellCacheEntry.initialTheme and the stored document
92
+ * serve every visitor, and ThemeProvider re-syncs only from an explicitly
93
+ * stored theme, so a visitor with none kept the first visitor's
94
+ * `useTheme().theme` for the page's lifetime. A visitor with a stored theme
95
+ * gets it pre-paint (theme script) and in the provider after mount.
96
+ *
97
+ * Any other render carries the visitor's theme (the unmatched-route 404 reads
98
+ * it without this function, rsc/handler.ts); when that differs from the
99
+ * default it marks `_payloadVisitorTheme`, and the document cache does not
100
+ * store the response.
91
101
  */
92
102
  export function payloadInitialTheme(
93
103
  reqCtx: Pick<
94
104
  RequestContext<any>,
95
- "_themeConfig" | "_shellCaptureRun" | "_readTheme"
105
+ | "_themeConfig"
106
+ | "_shellCaptureRun"
107
+ | "_readTheme"
108
+ | "_documentCacheRender"
109
+ | "_payloadVisitorTheme"
110
+ | "res"
96
111
  >,
97
112
  ): Theme | undefined {
98
113
  const config = reqCtx._themeConfig;
99
114
  if (!config) return undefined;
100
- return reqCtx._shellCaptureRun ? config.defaultTheme : reqCtx._readTheme();
115
+ if (reqCtx._shellCaptureRun || documentCacheStoresRender(reqCtx)) {
116
+ return config.defaultTheme;
117
+ }
118
+ const theme = reqCtx._readTheme();
119
+ if (theme !== config.defaultTheme) reqCtx._payloadVisitorTheme = true;
120
+ return theme;
101
121
  }
@@ -7,6 +7,7 @@
7
7
  * and progressive enhancement (no-JS form submissions).
8
8
  */
9
9
 
10
+ import { requestHeaders } from "../server/request-headers.js";
10
11
  import { isRouteNotFoundError } from "../errors.js";
11
12
  import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
12
13
  import {
@@ -31,7 +32,6 @@ import {
31
32
  } from "./helpers.js";
32
33
  import { renderRscFlightStage, renderRscResponse } from "./render-pipeline.js";
33
34
  import { guardOutgoingRedirect } from "./redirect-guard.js";
34
- import { payloadInitialTheme } from "./full-payload.js";
35
35
  import {
36
36
  resolveSoftRedirectUrl,
37
37
  resolveExternalRedirect,
@@ -925,8 +925,10 @@ export function createRSCHandler<
925
925
  originPhase,
926
926
  );
927
927
  if (originResult) {
928
+ const origin = requestHeaders(request).get("origin");
929
+ const host = requestHeaders(request).get("host");
928
930
  const originError = new Error(
929
- `Origin check rejected: ${request.headers.get("origin") ?? "none"} vs ${request.headers.get("host") ?? "none"}`,
931
+ `Origin check rejected: ${origin ?? "none"} vs ${host ?? "none"}`,
930
932
  );
931
933
  originError.name = "OriginCheckError";
932
934
 
@@ -935,11 +937,7 @@ export function createRSCHandler<
935
937
  url,
936
938
  env,
937
939
  handledByBoundary: false,
938
- metadata: {
939
- phase: originPhase,
940
- origin: request.headers.get("origin"),
941
- host: request.headers.get("host"),
942
- },
940
+ metadata: { phase: originPhase, origin, host },
943
941
  });
944
942
 
945
943
  if (router.telemetry) {
@@ -950,8 +948,8 @@ export function createRSCHandler<
950
948
  method: request.method,
951
949
  pathname: url.pathname,
952
950
  phase: originPhase,
953
- origin: request.headers.get("origin"),
954
- host: request.headers.get("host"),
951
+ origin,
952
+ host,
955
953
  });
956
954
  }
957
955
 
@@ -1367,7 +1365,11 @@ export function createRSCHandler<
1367
1365
  themeConfig: router.themeConfig,
1368
1366
  warmupEnabled: router.warmupEnabled,
1369
1367
  strictMode: router.strictMode,
1370
- initialTheme: payloadInitialTheme(getRequestContext()),
1368
+ // The visitor's theme, not payloadInitialTheme (full-payload.ts):
1369
+ // that picks the default when middleware opted the URL into the
1370
+ // document cache before next(), but a 404 is never stored or
1371
+ // captured (#978).
1372
+ initialTheme: getRequestContext()._readTheme(),
1371
1373
  },
1372
1374
  };
1373
1375
 
@@ -11,6 +11,7 @@
11
11
  * has already extracted params from it.
12
12
  */
13
13
 
14
+ import { requestHeaders } from "../server/request-headers.js";
14
15
  import { getLoaderLazy } from "../server/loader-registry.js";
15
16
  import { isDataNotFoundError } from "../errors.js";
16
17
  import { executeLoaderMiddleware } from "../router/middleware.js";
@@ -144,7 +145,7 @@ export async function handleLoaderFetch<TEnv>(
144
145
 
145
146
  if (isBodyMethod) {
146
147
  try {
147
- const contentType = request.headers.get("content-type") || "";
148
+ const contentType = requestHeaders(request).get("content-type") || "";
148
149
  if (contentType.includes("multipart/form-data")) {
149
150
  // FormData body — sent by load() when body is a FormData instance.
150
151
  // Preserves File objects and binary data.
@@ -14,6 +14,7 @@ import {
14
14
  setRouterPrecomputedEntries,
15
15
  } from "../route-map-builder.js";
16
16
  import { mergeFullManifests } from "../build/merge-full-manifests.js";
17
+ import { isTransitionWhenValidationEnabled } from "../transition-when-ref.js";
17
18
 
18
19
  /**
19
20
  * Build a fresh route trie from the router's URL-pattern mounts and store it in
@@ -49,6 +50,9 @@ export async function buildRouterTrieFromUrlpatterns(
49
50
  await generateManifestFull(mount.patterns, mount.mountIndex, {
50
51
  routerId: router.id,
51
52
  ...(router.basename ? { urlPrefix: router.basename } : {}),
53
+ // Under the Vite plugin the realm validates like discovery does, so
54
+ // a Cloudflare dev rebuild fails on an invalid when too.
55
+ validateTransitionWhen: isTransitionWhenValidationEnabled(),
52
56
  }),
53
57
  );
54
58
  }
@@ -9,6 +9,7 @@
9
9
  * navigations, bookmarks, and non-browser clients don't send Origin.
10
10
  */
11
11
 
12
+ import { requestHeaders } from "../server/request-headers.js";
12
13
  import type { RequestPlan } from "../router/request-classification.js";
13
14
 
14
15
  /**
@@ -69,10 +70,11 @@ export type OriginCheckConfig<TEnv = any> =
69
70
  * Returns true to allow, false to reject.
70
71
  */
71
72
  export function defaultOriginCheck(request: Request, url: URL): boolean {
72
- let requestOrigin = request.headers.get("origin");
73
+ const headers = requestHeaders(request);
74
+ let requestOrigin = headers.get("origin");
73
75
 
74
76
  if (!requestOrigin) {
75
- const referer = request.headers.get("referer");
77
+ const referer = headers.get("referer");
76
78
  if (referer) {
77
79
  try {
78
80
  requestOrigin = new URL(referer).origin;
@@ -92,7 +94,7 @@ export function defaultOriginCheck(request: Request, url: URL): boolean {
92
94
  // :authority), so a missing Host here is anomalous. Fail closed rather than
93
95
  // fall back to url.host (derived from the request line) when the trusted Host
94
96
  // cannot be established.
95
- const expectedHost = request.headers.get("host");
97
+ const expectedHost = headers.get("host");
96
98
  if (!expectedHost) return false;
97
99
 
98
100
  const expectedOrigin = `${url.protocol}//${expectedHost}`;
@@ -102,11 +104,12 @@ export function defaultOriginCheck(request: Request, url: URL): boolean {
102
104
 
103
105
  function createForbiddenResponse(request: Request): Response {
104
106
  const isDev = process.env.NODE_ENV !== "production";
107
+ const headers = requestHeaders(request);
105
108
  const body = isDev
106
109
  ? "Forbidden: Origin mismatch. The request origin does not match the server host. " +
107
110
  `Set originCheck: false in createRouter() to disable this check. ` +
108
- `(Origin: ${request.headers.get("origin") ?? "none"}, ` +
109
- `Host: ${request.headers.get("host") ?? "none"})`
111
+ `(Origin: ${headers.get("origin") ?? "none"}, ` +
112
+ `Host: ${headers.get("host") ?? "none"})`
110
113
  : "Forbidden";
111
114
 
112
115
  return new Response(body, {
@@ -6,6 +6,7 @@
6
6
  * reference. We detect these and return HTML instead of RSC stream.
7
7
  */
8
8
 
9
+ import { requestHeaders } from "../server/request-headers.js";
9
10
  import {
10
11
  getRequestContext,
11
12
  setRequestContextParams,
@@ -14,7 +15,7 @@ import { createSsrHtmlStage } from "./ssr-setup.js";
14
15
  import type { MiddlewareFn } from "../router/middleware.js";
15
16
  import { executeMiddleware } from "../router/middleware.js";
16
17
  import { observePhase, PHASES } from "../router/instrument.js";
17
- import { gateTransitions } from "./transition-gate.js";
18
+ import { attachTransitionWhen } from "./attach-transition-when.js";
18
19
  import { payloadInitialTheme } from "./full-payload.js";
19
20
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
20
21
  import type { RscPayload, ReactFormState } from "./types.js";
@@ -64,7 +65,7 @@ export async function handleProgressiveEnhancement<TEnv>(
64
65
  nonce: string | undefined,
65
66
  routeMwInfo?: PeRouteMiddlewareInfo,
66
67
  ): Promise<Response | null> {
67
- const contentType = request.headers.get("content-type") || "";
68
+ const contentType = requestHeaders(request).get("content-type") || "";
68
69
  const isFormSubmission =
69
70
  contentType.includes("multipart/form-data") ||
70
71
  contentType.includes("application/x-www-form-urlencoded");
@@ -312,7 +313,7 @@ async function handleProgressiveEnhancementInner<TEnv>(
312
313
  // custom headers) so loaders that read request headers/cookies behave
313
314
  // identically under PE and the JS action path. Drop body-framing headers
314
315
  // from the bodyless GET and force the HTML accept.
315
- const headers = new Headers(request.headers);
316
+ const headers = new Headers(requestHeaders(request));
316
317
  headers.delete("content-type");
317
318
  headers.delete("content-length");
318
319
  headers.delete("content-encoding");
@@ -326,15 +327,9 @@ async function handleProgressiveEnhancementInner<TEnv>(
326
327
  // JS/PE parity: this is an action's revalidation render, so mark it BEFORE
327
328
  // matching — a stale `foregroundOnAction` cache entry must re-execute in the
328
329
  // foreground during the re-render, exactly as the JS path's
329
- // revalidateAfterAction does. PPR transition({ when }) runs during match,
330
- // while foregroundOnAction reads _inActionRevalidation there too, so all
331
- // action metadata must be available before matching.
330
+ // revalidateAfterAction does.
332
331
  const peReqCtx = getRequestContext();
333
332
  peReqCtx._inActionRevalidation = true;
334
- peReqCtx._gateActionId = directActionId ?? undefined;
335
- peReqCtx._gateActionUrl = new URL(url);
336
- peReqCtx._gateActionResult = actionResult;
337
- peReqCtx._gateFormData = formData;
338
333
 
339
334
  return runRoutine(
340
335
  peRenderPlan({
@@ -427,11 +422,7 @@ function renderPeResponse<TEnv>(
427
422
  pathname: url.pathname,
428
423
  routerId: ctx.router.id,
429
424
  basename: ctx.router.basename,
430
- segments: gateTransitions(
431
- match.segments,
432
- getRequestContext(),
433
- ctx.router.onError,
434
- ),
425
+ segments: attachTransitionWhen(match.segments, getRequestContext()),
435
426
  matched: match.matched,
436
427
  diff: match.diff,
437
428
  resolvedIds: match.resolvedIds,
@@ -603,16 +594,6 @@ async function matchPeErrorBoundary<TEnv>(
603
594
 
604
595
  setRequestContextParams(errorResult.params, errorResult.routeName);
605
596
 
606
- // Only the failing action id + URL are in scope here (no formData/actionResult
607
- // thread into this helper). Expose the URL only when the action id is known:
608
- // this helper also handles malformed form bodies before action detection, and
609
- // those should not look like action-triggered renders to transition({ when }).
610
- if (actionId != null) {
611
- const peErrCtx = getRequestContext();
612
- peErrCtx._gateActionId = actionId;
613
- peErrCtx._gateActionUrl = new URL(url);
614
- }
615
-
616
597
  return errorResult;
617
598
  }
618
599
 
@@ -628,11 +609,7 @@ function renderPeErrorResponse<TEnv>(
628
609
  pathname: url.pathname,
629
610
  routerId: ctx.router.id,
630
611
  basename: ctx.router.basename,
631
- segments: gateTransitions(
632
- errorResult.segments,
633
- getRequestContext(),
634
- ctx.router.onError,
635
- ),
612
+ segments: attachTransitionWhen(errorResult.segments, getRequestContext()),
636
613
  matched: errorResult.matched,
637
614
  diff: errorResult.diff,
638
615
  resolvedIds: errorResult.resolvedIds,