@rangojs/router 0.24.4 → 0.26.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 (301) hide show
  1. package/README.md +1 -0
  2. package/dist/bin/rango.js +363 -39
  3. package/dist/testing/vitest.js +37 -4
  4. package/dist/types/browser/event-controller.d.ts +24 -5
  5. package/dist/types/browser/intercept-utils.d.ts +5 -2
  6. package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
  7. package/dist/types/browser/navigation-transaction.d.ts +5 -5
  8. package/dist/types/browser/partial-update.d.ts +21 -13
  9. package/dist/types/browser/prefetch/cache.d.ts +8 -0
  10. package/dist/types/browser/rango-marker.d.ts +7 -0
  11. package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
  12. package/dist/types/browser/react/use-handle.d.ts +20 -0
  13. package/dist/types/browser/react/use-href.d.ts +6 -0
  14. package/dist/types/browser/react/use-router.d.ts +6 -1
  15. package/dist/types/browser/response-adapter.d.ts +5 -3
  16. package/dist/types/browser/scroll-restoration.d.ts +9 -3
  17. package/dist/types/browser/segment-reconciler.d.ts +10 -13
  18. package/dist/types/browser/types.d.ts +38 -8
  19. package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
  22. package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
  23. package/dist/types/cache/handle-snapshot.d.ts +2 -2
  24. package/dist/types/cache/types.d.ts +16 -6
  25. package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
  26. package/dist/types/client-urls/client-urls.d.ts +6 -2
  27. package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
  28. package/dist/types/client-urls/server-projection.d.ts +9 -5
  29. package/dist/types/client-urls/types.d.ts +29 -16
  30. package/dist/types/client.d.ts +2 -2
  31. package/dist/types/client.rsc.d.ts +2 -2
  32. package/dist/types/decode-loader-results.d.ts +28 -7
  33. package/dist/types/handles/deferred-resolution.d.ts +3 -2
  34. package/dist/types/handles/meta.d.ts +5 -0
  35. package/dist/types/href-client.d.ts +3 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/loader-redirect.d.ts +2 -3
  38. package/dist/types/outlet-context.d.ts +7 -6
  39. package/dist/types/prefetch-markers.d.ts +14 -5
  40. package/dist/types/prerender/build-shell-capture.d.ts +1 -2
  41. package/dist/types/prerender/writable-store.d.ts +2 -3
  42. package/dist/types/route-content-wrapper.d.ts +17 -14
  43. package/dist/types/route-definition/helpers-types.d.ts +7 -2
  44. package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
  45. package/dist/types/route-definition/rewrite.d.ts +48 -5
  46. package/dist/types/route-types.d.ts +2 -2
  47. package/dist/types/router/handler-context.d.ts +14 -2
  48. package/dist/types/router/intercept-resolution.d.ts +19 -46
  49. package/dist/types/router/lazy-includes.d.ts +7 -0
  50. package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
  51. package/dist/types/router/middleware.d.ts +0 -16
  52. package/dist/types/router/navigation-snapshot.d.ts +2 -0
  53. package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
  54. package/dist/types/router/revalidation.d.ts +8 -1
  55. package/dist/types/router/router-context.d.ts +4 -16
  56. package/dist/types/router/router-interfaces.d.ts +0 -11
  57. package/dist/types/router/router-options.d.ts +4 -27
  58. package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
  59. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
  60. package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
  61. package/dist/types/router/segment-resolution.d.ts +1 -1
  62. package/dist/types/router/segment-wrappers.d.ts +3 -5
  63. package/dist/types/router/substitute-pattern-params.d.ts +3 -2
  64. package/dist/types/router/tracing.d.ts +2 -2
  65. package/dist/types/router-versions.d.ts +8 -6
  66. package/dist/types/rsc/full-payload.d.ts +14 -2
  67. package/dist/types/rsc/helpers.d.ts +8 -0
  68. package/dist/types/rsc/manifest-init.d.ts +20 -0
  69. package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
  70. package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
  71. package/dist/types/rsc/rewrite-passes.d.ts +12 -2
  72. package/dist/types/rsc/server-action.d.ts +6 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +14 -4
  74. package/dist/types/rsc/shell-capture.d.ts +23 -2
  75. package/dist/types/rsc/types.d.ts +5 -8
  76. package/dist/types/segment-system.d.ts +15 -0
  77. package/dist/types/server/build-version-table.d.ts +10 -13
  78. package/dist/types/server/context.d.ts +90 -6
  79. package/dist/types/server/handle-store.d.ts +17 -6
  80. package/dist/types/server/request-context.d.ts +45 -7
  81. package/dist/types/server/request-headers.d.ts +12 -0
  82. package/dist/types/server/rewrite-trail.d.ts +8 -1
  83. package/dist/types/testing/build-versions.d.ts +0 -3
  84. package/dist/types/testing/e2e/index.d.ts +1 -1
  85. package/dist/types/testing/flight.entry.d.ts +1 -1
  86. package/dist/types/testing/index.d.ts +1 -1
  87. package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
  88. package/dist/types/testing/internal/seed-vars.d.ts +4 -4
  89. package/dist/types/testing/render-route.d.ts +21 -11
  90. package/dist/types/testing/run-loader.d.ts +7 -0
  91. package/dist/types/testing/run-middleware.d.ts +7 -0
  92. package/dist/types/testing/run-revalidate.d.ts +5 -4
  93. package/dist/types/testing/serve-shell-request.d.ts +42 -5
  94. package/dist/types/testing/shell-status.d.ts +17 -0
  95. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  96. package/dist/types/testing/vitest.d.ts +10 -1
  97. package/dist/types/thenable-status.d.ts +0 -6
  98. package/dist/types/types/handler-context.d.ts +12 -1
  99. package/dist/types/types/loader-types.d.ts +10 -4
  100. package/dist/types/types/route-entry.d.ts +2 -1
  101. package/dist/types/urls/path-helper-types.d.ts +4 -2
  102. package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
  103. package/dist/types/vite/discovery/state.d.ts +20 -0
  104. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
  105. package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
  106. package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
  107. package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
  108. package/dist/vite/index.js +6819 -6555
  109. package/package.json +1 -1
  110. package/skills/app-structure/SKILL.md +255 -0
  111. package/skills/app-structure/content-site.md +333 -0
  112. package/skills/app-structure/e-commerce.md +435 -0
  113. package/skills/app-structure/large-app.md +885 -0
  114. package/skills/app-structure/multi-app.md +280 -0
  115. package/skills/breadcrumbs/SKILL.md +6 -1
  116. package/skills/cache-guide/SKILL.md +4 -8
  117. package/skills/caching/SKILL.md +6 -6
  118. package/skills/catalog.json +12 -0
  119. package/skills/client-urls/SKILL.md +185 -32
  120. package/skills/comparison/references/framework-comparison.md +1 -1
  121. package/skills/composability/SKILL.md +10 -8
  122. package/skills/handler-use/SKILL.md +48 -9
  123. package/skills/hooks/handle-and-actions.md +104 -0
  124. package/skills/hooks/navigation.md +24 -10
  125. package/skills/hooks/urls.md +1 -1
  126. package/skills/host-router/SKILL.md +1 -1
  127. package/skills/i18n/SKILL.md +2 -1
  128. package/skills/intercept/SKILL.md +60 -29
  129. package/skills/layout/SKILL.md +6 -3
  130. package/skills/links/SKILL.md +151 -17
  131. package/skills/loader/SKILL.md +42 -19
  132. package/skills/middleware/SKILL.md +111 -15
  133. package/skills/observability/SKILL.md +11 -0
  134. package/skills/parallel/SKILL.md +5 -5
  135. package/skills/ppr/SKILL.md +103 -52
  136. package/skills/prerender/SKILL.md +4 -5
  137. package/skills/rango/SKILL.md +3 -0
  138. package/skills/response-routes/SKILL.md +4 -1
  139. package/skills/router-setup/SKILL.md +0 -2
  140. package/skills/scripts/SKILL.md +12 -1
  141. package/skills/server-actions/SKILL.md +60 -20
  142. package/skills/testing/SKILL.md +1 -0
  143. package/skills/testing/client-components.md +13 -10
  144. package/skills/testing/loader.md +25 -24
  145. package/skills/testing/middleware.md +11 -11
  146. package/skills/testing/render-handler.md +17 -17
  147. package/skills/testing/response-routes.md +4 -1
  148. package/skills/testing/serve-shell-request.md +86 -22
  149. package/skills/testing/server-actions.md +16 -16
  150. package/skills/ux-architecture/SKILL.md +980 -0
  151. package/skills/vercel/SKILL.md +1 -1
  152. package/skills/view-transitions/SKILL.md +43 -10
  153. package/src/__augment-tests__/augmented.check.ts +28 -0
  154. package/src/browser/event-controller.ts +44 -18
  155. package/src/browser/intercept-utils.ts +3 -2
  156. package/src/browser/merge-segment-loaders.ts +0 -85
  157. package/src/browser/navigation-bridge.ts +112 -84
  158. package/src/browser/navigation-client.ts +20 -15
  159. package/src/browser/navigation-store.ts +4 -2
  160. package/src/browser/navigation-transaction.ts +9 -8
  161. package/src/browser/partial-update.ts +90 -32
  162. package/src/browser/prefetch/cache.ts +8 -0
  163. package/src/browser/prefetch/fetch.ts +13 -3
  164. package/src/browser/rango-marker.ts +13 -0
  165. package/src/browser/react/NavigationProvider.tsx +25 -7
  166. package/src/browser/react/filter-segment-order.ts +13 -0
  167. package/src/browser/react/use-action.ts +9 -15
  168. package/src/browser/react/use-handle.ts +63 -71
  169. package/src/browser/react/use-href.tsx +24 -5
  170. package/src/browser/react/use-link-status.ts +1 -1
  171. package/src/browser/react/use-navigation.ts +30 -40
  172. package/src/browser/react/use-router.ts +37 -6
  173. package/src/browser/react/use-segments.ts +8 -12
  174. package/src/browser/response-adapter.ts +11 -5
  175. package/src/browser/rsc-router.tsx +3 -1
  176. package/src/browser/scroll-restoration.ts +34 -20
  177. package/src/browser/segment-reconciler.ts +11 -29
  178. package/src/browser/server-action-bridge.ts +52 -16
  179. package/src/browser/types.ts +40 -8
  180. package/src/build/generate-manifest.ts +12 -1
  181. package/src/build/route-types/codegen.ts +6 -2
  182. package/src/build/route-types/per-module-writer.ts +94 -61
  183. package/src/cache/cache-runtime.ts +9 -0
  184. package/src/cache/cache-scope.ts +2 -1
  185. package/src/cache/cf/cf-cache-store.ts +6 -7
  186. package/src/cache/cf/cf-cache-types.ts +0 -17
  187. package/src/cache/cf/cf-shell-frame.ts +3 -0
  188. package/src/cache/handle-snapshot.ts +2 -2
  189. package/src/cache/types.ts +16 -6
  190. package/src/cache/vercel/vercel-cache-store.ts +8 -19
  191. package/src/client-urls/client-urls.ts +202 -153
  192. package/src/client-urls/revalidate-registry.ts +12 -3
  193. package/src/client-urls/server-projection.ts +137 -66
  194. package/src/client-urls/types.ts +30 -15
  195. package/src/client.rsc.tsx +11 -1
  196. package/src/client.tsx +9 -1
  197. package/src/decode-loader-results.ts +64 -31
  198. package/src/handles/deferred-resolution.ts +3 -2
  199. package/src/handles/meta.ts +5 -0
  200. package/src/href-client.ts +3 -1
  201. package/src/index.rsc.ts +1 -0
  202. package/src/index.ts +12 -4
  203. package/src/loader-redirect.tsx +2 -3
  204. package/src/outlet-context.ts +7 -6
  205. package/src/prefetch-markers.ts +15 -5
  206. package/src/prerender/build-shell-capture.ts +1 -2
  207. package/src/prerender/writable-store.ts +2 -3
  208. package/src/reverse.ts +3 -3
  209. package/src/route-content-wrapper.tsx +19 -17
  210. package/src/route-definition/dsl-helpers.ts +41 -21
  211. package/src/route-definition/helpers-types.ts +7 -2
  212. package/src/route-definition/resolve-handler-use.ts +23 -10
  213. package/src/route-definition/rewrite.ts +86 -19
  214. package/src/route-types.ts +1 -2
  215. package/src/router/find-match.ts +37 -1
  216. package/src/router/handler-context.ts +85 -40
  217. package/src/router/intercept-resolution.ts +161 -307
  218. package/src/router/lazy-includes.ts +43 -27
  219. package/src/router/loader-resolution.ts +21 -1
  220. package/src/router/manifest.ts +15 -10
  221. package/src/router/match-api.ts +11 -1
  222. package/src/router/match-middleware/background-revalidation.ts +2 -4
  223. package/src/router/match-middleware/cache-lookup.ts +16 -20
  224. package/src/router/match-middleware/intercept-resolution.ts +41 -60
  225. package/src/router/match-result.ts +31 -2
  226. package/src/router/middleware.ts +6 -147
  227. package/src/router/navigation-snapshot.ts +38 -2
  228. package/src/router/prefetch-cache-ttl.ts +10 -18
  229. package/src/router/prerender-match.ts +2 -1
  230. package/src/router/revalidation.ts +24 -3
  231. package/src/router/router-context.ts +5 -18
  232. package/src/router/router-interfaces.ts +0 -13
  233. package/src/router/router-options.ts +4 -28
  234. package/src/router/segment-resolution/fresh.ts +6 -2
  235. package/src/router/segment-resolution/helpers.ts +7 -1
  236. package/src/router/segment-resolution/loader-cache.ts +4 -3
  237. package/src/router/segment-resolution/mask-nested.ts +5 -3
  238. package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
  239. package/src/router/segment-resolution/revalidation.ts +75 -35
  240. package/src/router/segment-resolution.ts +0 -1
  241. package/src/router/segment-wrappers.ts +8 -13
  242. package/src/router/substitute-pattern-params.ts +3 -2
  243. package/src/router/tracing.ts +2 -2
  244. package/src/router-versions.ts +7 -7
  245. package/src/router.ts +5 -17
  246. package/src/rsc/full-payload.ts +26 -4
  247. package/src/rsc/handler.ts +86 -48
  248. package/src/rsc/helpers.ts +13 -0
  249. package/src/rsc/manifest-init.ts +56 -3
  250. package/src/rsc/prefetch-render-error.ts +38 -0
  251. package/src/rsc/progressive-enhancement.ts +161 -211
  252. package/src/rsc/rewrite-passes.ts +56 -9
  253. package/src/rsc/rsc-rendering.ts +79 -23
  254. package/src/rsc/server-action.ts +25 -2
  255. package/src/rsc/shell-capture-constants.ts +26 -3
  256. package/src/rsc/shell-capture.ts +137 -51
  257. package/src/rsc/types.ts +5 -9
  258. package/src/segment-system.tsx +125 -119
  259. package/src/server/build-version-table.ts +12 -24
  260. package/src/server/context.ts +160 -7
  261. package/src/server/handle-store.ts +80 -12
  262. package/src/server/request-context.ts +46 -6
  263. package/src/server/request-headers.ts +32 -0
  264. package/src/server/rewrite-trail.ts +55 -9
  265. package/src/ssr/ssr-root.tsx +6 -10
  266. package/src/testing/build-versions.ts +0 -3
  267. package/src/testing/dispatch.ts +5 -8
  268. package/src/testing/e2e/index.ts +1 -0
  269. package/src/testing/flight.entry.ts +1 -0
  270. package/src/testing/index.ts +1 -0
  271. package/src/testing/internal/client-urls-projection.ts +31 -11
  272. package/src/testing/internal/seed-vars.ts +4 -4
  273. package/src/testing/render-route.tsx +83 -40
  274. package/src/testing/run-loader.ts +26 -2
  275. package/src/testing/run-middleware.ts +10 -0
  276. package/src/testing/run-revalidate.ts +13 -6
  277. package/src/testing/serve-shell-request.ts +79 -9
  278. package/src/testing/shell-status.ts +40 -0
  279. package/src/testing/vitest-stubs/version.ts +1 -0
  280. package/src/testing/vitest.ts +74 -7
  281. package/src/thenable-status.ts +0 -11
  282. package/src/types/handler-context.ts +12 -1
  283. package/src/types/loader-types.ts +10 -4
  284. package/src/types/route-entry.ts +2 -1
  285. package/src/urls/include-helper.ts +38 -11
  286. package/src/urls/path-helper-types.ts +4 -2
  287. package/src/vite/discovery/client-urls-projection.ts +205 -16
  288. package/src/vite/discovery/discover-routers.ts +2 -2
  289. package/src/vite/discovery/prerender-collection.ts +2 -6
  290. package/src/vite/discovery/shell-prerender-phase.ts +1 -5
  291. package/src/vite/discovery/state.ts +26 -0
  292. package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
  293. package/src/vite/plugins/expose-internal-ids.ts +46 -40
  294. package/src/vite/plugins/hoist/source.ts +8 -7
  295. package/src/vite/plugins/transition-when-hoist.ts +1 -0
  296. package/src/vite/plugins/version-plugin.ts +7 -0
  297. package/src/vite/plugins/version.d.ts +7 -0
  298. package/src/vite/plugins/virtual-entries.ts +20 -0
  299. package/src/vite/rango.ts +4 -1
  300. package/src/vite/router-discovery.ts +39 -7
  301. package/src/vite/utils/prerender-utils.ts +17 -0
@@ -15,6 +15,7 @@ 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
17
  import {
18
+ pathKey,
18
19
  rendersForSharedAudience,
19
20
  rewriteTrail,
20
21
  visitorUrl,
@@ -42,7 +43,7 @@ export function buildFullPayload(
42
43
  ): RscPayload {
43
44
  return {
44
45
  metadata: {
45
- pathname: renderedLocation(reqCtx, url).pathname,
46
+ ...payloadLocation(reqCtx, url),
46
47
  routerId: ctx.router.id,
47
48
  basename: ctx.router.basename,
48
49
  segments: attachTransitionWhen(m.segments, reqCtx),
@@ -95,7 +96,8 @@ export function buildFullPayload(
95
96
  * bar) agree on `usePathname()` and `useSearchParams()`. A render other
96
97
  * visitors may be served shows `url`, the target, as a direct visit does: a
97
98
  * 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
+ * target), except a visitor-keyed shell capture, whose key covers the
100
+ * visitor's URL. Any other render that shows the visitor's URL marks
99
101
  * `_payloadVisitorLocation`, and the document cache does not store it.
100
102
  */
101
103
  export function renderedLocation(
@@ -104,6 +106,7 @@ export function renderedLocation(
104
106
  RequestContext<any>,
105
107
  | "_variables"
106
108
  | "_shellCaptureRun"
109
+ | "_shellCaptureForVisitor"
107
110
  | "_documentCacheRender"
108
111
  | "_payloadVisitorLocation"
109
112
  | "res"
@@ -111,13 +114,32 @@ export function renderedLocation(
111
114
  | undefined,
112
115
  url: URL,
113
116
  ): URL {
114
- if (!reqCtx || !rewriteTrail(reqCtx) || rendersForSharedAudience(reqCtx)) {
115
- return url;
117
+ if (!reqCtx || !rewriteTrail(reqCtx)) return url;
118
+ if (rendersForSharedAudience(reqCtx)) {
119
+ return reqCtx._shellCaptureForVisitor ? visitorUrl(reqCtx, url) : url;
116
120
  }
117
121
  reqCtx._payloadVisitorLocation = true;
118
122
  return visitorUrl(reqCtx, url);
119
123
  }
120
124
 
125
+ /**
126
+ * A payload's `pathname` (renderedLocation) and, when middleware rewrote the
127
+ * request, `rewriteTarget`: the path and search the route rendered (the
128
+ * trail's last URL). The browser keeps it per history entry and sends it
129
+ * back as `_rsc_rt`, so the next request from the page diffs against the
130
+ * route that rendered it (navigation-snapshot.ts resolveNavigation, #1144).
131
+ */
132
+ export function payloadLocation(
133
+ reqCtx: Parameters<typeof renderedLocation>[0],
134
+ url: URL,
135
+ ): { pathname: string; rewriteTarget?: string } {
136
+ const pathname = renderedLocation(reqCtx, url).pathname;
137
+ const trail = rewriteTrail(reqCtx);
138
+ return trail
139
+ ? { pathname, rewriteTarget: pathKey(new URL(trail[trail.length - 1]!)) }
140
+ : { pathname };
141
+ }
142
+
121
143
  /**
122
144
  * The theme a document payload carries as `metadata.initialTheme` (the
123
145
  * ThemeProvider's first state), read with no identity guard: it is the
@@ -18,7 +18,8 @@ import {
18
18
  captureRequestEntryContext,
19
19
  } from "../server/request-context.js";
20
20
  import { REWRITE_HEADER, runRewritePasses } from "./rewrite-passes.js";
21
- import { renderedLocation } from "./full-payload.js";
21
+ import { rewriteTrail } from "../server/rewrite-trail.js";
22
+ import { payloadLocation } from "./full-payload.js";
22
23
  import * as rscDeps from "@vitejs/plugin-rsc/rsc/server";
23
24
  import type {
24
25
  RscPayload,
@@ -27,6 +28,7 @@ import type {
27
28
  SSRModule,
28
29
  } from "./types.js";
29
30
  import {
31
+ actionRenderRequest,
30
32
  createReloadResponse,
31
33
  createResponseWithMergedHeaders,
32
34
  finalizeResponse,
@@ -79,6 +81,7 @@ import type { SegmentCacheStore } from "../cache/types.js";
79
81
  import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
80
82
  import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
81
83
  import {
84
+ continuationForRewritePass,
82
85
  executeServerAction,
83
86
  revalidateAfterAction,
84
87
  type ActionContinuation,
@@ -162,12 +165,8 @@ export function createRSCHandler<
162
165
  TRoutes extends Record<string, string> = Record<string, string>,
163
166
  >(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
164
167
  const { router, nonce: nonceProvider } = options;
165
- // A consumer-set version (this option, else createRouter({ version })) is
166
- // used for both; otherwise the router's own pair from the build.
167
- const versions = resolveRouterVersions(
168
- router.id,
169
- options.version ?? router.version,
170
- );
168
+ // The router's own pair from the build.
169
+ const versions = resolveRouterVersions(router.id);
171
170
  // Everything the handler itself versions is tied to the HTML a tab holds:
172
171
  // payload metadata (what the browser echoes as `_rsc_v`), the reload check,
173
172
  // shell stamping and gating, the rango state value. The data version is only
@@ -545,7 +544,7 @@ export function createRSCHandler<
545
544
  resolvedPrerender = {
546
545
  config: prerenderConfig,
547
546
  routerId: router.id,
548
- version: resolvePrerenderVersion(router.id, router.version),
547
+ version: resolvePrerenderVersion(router.id),
549
548
  };
550
549
  }
551
550
  }
@@ -973,6 +972,32 @@ export function createRSCHandler<
973
972
  // "middleware-short-circuit").
974
973
  getRequestContext()._requestMode = plan.mode;
975
974
 
975
+ // A rewritten pass of an action must reach the branch that runs it: the
976
+ // one that ran it re-renders from its outcome, and a JS action a global
977
+ // rewrite moved before it ran runs only on the action branch. Any other
978
+ // plan would serve the POST as a fresh request or drop the action; a
979
+ // reload (version mismatch, app switch) does neither.
980
+ const passCtx = getRequestContext();
981
+ const actionRan = passCtx._actionRan;
982
+ const actionMode = actionRan
983
+ ? actionRan.kind === "action"
984
+ ? "action"
985
+ : "pe-render"
986
+ : url.searchParams.has("_rsc_action") && rewriteTrail(passCtx)
987
+ ? "action"
988
+ : undefined;
989
+ if (
990
+ actionMode &&
991
+ plan.mode !== actionMode &&
992
+ plan.mode !== "version-mismatch" &&
993
+ plan.mode !== "app-switch"
994
+ ) {
995
+ throw new Error(
996
+ `A rewrite of a server action or form post must target a page ` +
997
+ `route; ${url.pathname} ${plan.mode === "response" ? "is a response route" : plan.mode === "redirect" ? "redirects" : "is not one"}.`,
998
+ );
999
+ }
1000
+
976
1001
  // ---- 2. Terminal plans (no execution needed) ----
977
1002
  if (plan.mode === "redirect") {
978
1003
  // Redirects are handled by the pipeline (match/matchPartial),
@@ -1224,51 +1249,64 @@ export function createRSCHandler<
1224
1249
 
1225
1250
  // ---- Action: execute action, then revalidate wrapped in route middleware ----
1226
1251
  if (plan.mode === "action") {
1227
- let actionContinuation: ActionContinuation | undefined;
1228
- try {
1229
- // Instrument the action execution as its own phase (action:<actionId> +
1230
- // rango.action), so a POST shows the mutation time AND which action ran,
1231
- // not just the downstream revalidation render. The action's own
1232
- // loaders/fetches nest under rango.action.
1233
- const actionOutcome = await withTimeout(
1234
- observePhase(PHASES.action(plan.actionId), () =>
1235
- executeServerAction(
1236
- handlerCtx,
1252
+ const reqCtx = getRequestContext();
1253
+ const ran = reqCtx._actionRan;
1254
+ let actionContinuation: ActionContinuation;
1255
+ if (ran?.kind === "action") {
1256
+ // A rewrite pass after the action ran: never run it again.
1257
+ actionContinuation = await continuationForRewritePass(
1258
+ handlerCtx,
1259
+ request,
1260
+ env,
1261
+ ran.continuation,
1262
+ );
1263
+ } else {
1264
+ try {
1265
+ // Instrument the action execution as its own phase (action:<actionId> +
1266
+ // rango.action), so a POST shows the mutation time AND which action ran,
1267
+ // not just the downstream revalidation render. The action's own
1268
+ // loaders/fetches nest under rango.action.
1269
+ const actionOutcome = await withTimeout(
1270
+ observePhase(PHASES.action(plan.actionId), () =>
1271
+ executeServerAction(
1272
+ handlerCtx,
1273
+ request,
1274
+ env,
1275
+ url,
1276
+ plan.actionId,
1277
+ handleStore,
1278
+ ),
1279
+ ),
1280
+ router.timeouts.actionMs,
1281
+ "action",
1282
+ );
1283
+ if (actionOutcome.timedOut) {
1284
+ return handleTimeoutResponse(
1237
1285
  request,
1238
1286
  env,
1239
1287
  url,
1288
+ "action",
1289
+ actionOutcome.durationMs,
1290
+ plan.route.routeKey,
1240
1291
  plan.actionId,
1241
- handleStore,
1242
- ),
1243
- ),
1244
- router.timeouts.actionMs,
1245
- "action",
1246
- );
1247
- if (actionOutcome.timedOut) {
1248
- return handleTimeoutResponse(
1292
+ );
1293
+ }
1294
+ const result = actionOutcome.result;
1295
+ // Response means redirect or error boundary — done.
1296
+ if (result instanceof Response) return result;
1297
+ actionContinuation = result;
1298
+ reqCtx._actionRan = { kind: "action", continuation: result };
1299
+ } catch (error) {
1300
+ callOnError(error, "action", {
1249
1301
  request,
1250
- env,
1251
1302
  url,
1252
- "action",
1253
- actionOutcome.durationMs,
1254
- plan.route.routeKey,
1255
- plan.actionId,
1256
- );
1303
+ env,
1304
+ actionId: plan.actionId,
1305
+ handledByBoundary: false,
1306
+ });
1307
+ console.error(`[RSC] Action error:`, error);
1308
+ throw error;
1257
1309
  }
1258
- const result = actionOutcome.result;
1259
- // Response means redirect or error boundary — done.
1260
- if (result instanceof Response) return result;
1261
- actionContinuation = result;
1262
- } catch (error) {
1263
- callOnError(error, "action", {
1264
- request,
1265
- url,
1266
- env,
1267
- actionId: plan.actionId,
1268
- handledByBoundary: false,
1269
- });
1270
- console.error(`[RSC] Action error:`, error);
1271
- throw error;
1272
1310
  }
1273
1311
 
1274
1312
  // Revalidation render wrapped in route middleware.
@@ -1423,7 +1461,7 @@ export function createRSCHandler<
1423
1461
 
1424
1462
  const payload: RscPayload = {
1425
1463
  metadata: {
1426
- pathname: renderedLocation(getRequestContext(), url).pathname,
1464
+ ...payloadLocation(getRequestContext(), url),
1427
1465
  routerId: router.id,
1428
1466
  basename: router.basename,
1429
1467
  segments: [notFoundSegment],
@@ -1527,7 +1565,7 @@ export function createRSCHandler<
1527
1565
  if (routeMiddleware.length > 0) {
1528
1566
  const mwResponse = await executeMiddleware(
1529
1567
  buildRouteMiddlewareEntries<TEnv>(routeMiddleware),
1530
- request,
1568
+ actionContinuation ? actionRenderRequest(request) : request,
1531
1569
  env,
1532
1570
  variables,
1533
1571
  renderHandler,
@@ -25,6 +25,8 @@ import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
25
25
  import { formatCacheSignalHeader } from "../router/telemetry.js";
26
26
  import type { RscPayload } from "./types.js";
27
27
  import { rewriteTrail, visitorUrl } from "../server/rewrite-trail.js";
28
+ import { guardRequestHeaders } from "../server/cookie-store.js";
29
+ import { bodylessRequest } from "../server/request-headers.js";
28
30
  import type { HandlerContext } from "./handler-context.js";
29
31
 
30
32
  /**
@@ -380,6 +382,17 @@ export function buildRouteMiddlewareEntries<TEnv>(
380
382
  }));
381
383
  }
382
384
 
385
+ /**
386
+ * The request route middleware sees around the render after a server action
387
+ * or a no-JS form post: the page's GET (URL, headers without body framing or
388
+ * content-type, signal, workerd `cf`; no body). Route middleware never wraps the action,
389
+ * only that render, so it decides as for a GET of the page: a rewrite it
390
+ * skips for mutations still moves the render to its target (#1145).
391
+ */
392
+ export function actionRenderRequest(request: Request): Request {
393
+ return guardRequestHeaders(bodylessRequest(request.url, request, "GET"));
394
+ }
395
+
383
396
  /**
384
397
  * Merge stub headers from the request context onto an existing Response in
385
398
  * place, then drain onResponse callbacks. Used when a Response cannot flow
@@ -6,8 +6,12 @@
6
6
  */
7
7
 
8
8
  import {
9
+ ensureRouterManifest,
9
10
  getGlobalRouteMap,
11
+ getRouterManifest,
12
+ getRouterTrie,
10
13
  hasCachedManifest,
14
+ isRouterTrieAuthoritative,
11
15
  setCachedManifest,
12
16
  setRouterManifest,
13
17
  setRouterTrie,
@@ -15,6 +19,55 @@ import {
15
19
  } from "../route-map-builder.js";
16
20
  import { mergeFullManifests } from "../build/merge-full-manifests.js";
17
21
  import { isTransitionWhenValidationEnabled } from "../transition-when-ref.js";
22
+ import type { TrieNode } from "../router/route-trie-builder.js";
23
+
24
+ // A zero-route router builds no trie; recording that keeps it from rebuilding.
25
+ const NO_TRIE: unique symbol = Symbol("rango.noTrie");
26
+
27
+ // The trie each router object last installed. Router ids need not be unique
28
+ // across instances (a test helper calling createRouter({ id }) per test), and
29
+ // the per-id trie map cannot tell which instance a trie came from.
30
+ const installedTries = new WeakMap<object, TrieNode | typeof NO_TRIE>();
31
+
32
+ /** True when the trie installed for `router.id` was built from this router. */
33
+ export function isRouterTrieBuiltFrom(router: { id?: string }): boolean {
34
+ if (router.id === undefined) return false;
35
+ const installed = installedTries.get(router);
36
+ return (
37
+ installed !== undefined &&
38
+ installed === (getRouterTrie(router.id) ?? NO_TRIE)
39
+ );
40
+ }
41
+
42
+ /** The router fields ensureRouterTrie reads. */
43
+ export interface TrieSource {
44
+ id?: string;
45
+ urlpatterns?: unknown;
46
+ routeMap: Record<string, unknown>;
47
+ }
48
+
49
+ /**
50
+ * Install the trie `router` matches through, as a request finds it: the build
51
+ * manifest's trie when one is registered for its id, otherwise one built from
52
+ * its routes the way the dev handler builds it (rsc/handler.ts). Ids can repeat
53
+ * across router objects (tests), and another object's trie names other routes,
54
+ * so a trie this object did not build is rebuilt. Seeds the per-router manifest
55
+ * from `router.routeMap` when neither source set one, for reverse(). testing's
56
+ * dispatch matches through it, never the regex fallback alone (#1104, #1107).
57
+ */
58
+ export async function ensureRouterTrie(router: TrieSource): Promise<void> {
59
+ const id = router.id;
60
+ if (!id) return;
61
+ if (!isRouterTrieAuthoritative(id) && !isRouterTrieBuiltFrom(router)) {
62
+ await ensureRouterManifest(id);
63
+ if (router.urlpatterns && !isRouterTrieAuthoritative(id)) {
64
+ await buildRouterTrieFromUrlpatterns(router);
65
+ }
66
+ }
67
+ if (!getRouterManifest(id)) {
68
+ setRouterManifest(id, router.routeMap as Record<string, string>);
69
+ }
70
+ }
18
71
 
19
72
  /**
20
73
  * Build a fresh route trie from the router's URL-pattern mounts and store it in
@@ -63,9 +116,9 @@ export async function buildRouterTrieFromUrlpatterns(
63
116
  // are no routes.
64
117
  const { buildPerRouterTrie } = await import("../build/route-trie.js");
65
118
  const trie = buildPerRouterTrie(generated);
66
- if (trie) {
67
- setRouterTrie(router.id, trie);
68
- }
119
+ if (trie) setRouterTrie(router.id, trie);
120
+ // Re-read: setRouterTrie stores a copy (nullPrototypeStaticChildren).
121
+ installedTries.set(router, trie ? getRouterTrie(router.id)! : NO_TRIE);
69
122
  setRouterManifest(router.id, generated.routeManifest);
70
123
 
71
124
  // Match the production discovery path: precompute leaf-include entries so the
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A prefetch whose render reported an error is not kept (#1170).
3
+ *
4
+ * WHY: its 200 left with the headers before a streamed part failed, so the
5
+ * client stored the error boundary as a healthy prefetch, served it to a
6
+ * click and respawned it. The status cannot change any more, and the body
7
+ * cannot end with an error: workerd delivers an errored body as a clean end,
8
+ * and in Chromium the error drops the rows a click that adopted the prefetch
9
+ * has not decoded yet (a Flight "network error" in place of the page).
10
+ *
11
+ * So the body ends cleanly with one more row, PREFETCH_FAILED_ROW, which the
12
+ * Flight decoder skips and teeWithCompletion (browser/response-adapter.ts)
13
+ * reads as a stream that did not end cleanly: the client evicts the prefetch
14
+ * and never respawns it. Prefetch responses carry no Cache-Control, so the
15
+ * browser HTTP cache keeps none of it either.
16
+ *
17
+ * The errors are the render's `_renderErrors`: a Flight error row, a streamed
18
+ * handler recovered into its boundary, a loader that threw (redirect() and
19
+ * notFound() are not errors). Each is recorded before Flight writes the row
20
+ * that carries it, so the list is complete once Flight closes the stream.
21
+ */
22
+
23
+ import { PREFETCH_FAILED_ROW } from "../prefetch-markers.js";
24
+
25
+ export function markPrefetchOnRenderError(
26
+ response: Response,
27
+ renderErrors: readonly unknown[],
28
+ ): Response {
29
+ if (!response.body) return response;
30
+ const marked = new TransformStream<Uint8Array, Uint8Array>({
31
+ flush(controller) {
32
+ if (renderErrors.length > 0) {
33
+ controller.enqueue(new TextEncoder().encode(PREFETCH_FAILED_ROW));
34
+ }
35
+ },
36
+ });
37
+ return new Response(response.body.pipeThrough(marked), response);
38
+ }