@rangojs/router 0.24.3 → 0.25.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 (274) 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-shell-frame.d.ts +2 -0
  21. package/dist/types/cache/types.d.ts +8 -0
  22. package/dist/types/client-urls/client-root.d.ts +4 -1
  23. package/dist/types/client-urls/client-urls.d.ts +28 -2
  24. package/dist/types/client-urls/navigation.d.ts +6 -8
  25. package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
  26. package/dist/types/client-urls/server-projection.d.ts +20 -5
  27. package/dist/types/client-urls/types.d.ts +35 -17
  28. package/dist/types/client.d.ts +2 -2
  29. package/dist/types/client.rsc.d.ts +2 -2
  30. package/dist/types/decode-loader-results.d.ts +28 -7
  31. package/dist/types/href-client.d.ts +3 -1
  32. package/dist/types/index.rsc.d.ts +1 -1
  33. package/dist/types/loader-redirect.d.ts +2 -3
  34. package/dist/types/outlet-context.d.ts +7 -6
  35. package/dist/types/prefetch-markers.d.ts +14 -5
  36. package/dist/types/route-content-wrapper.d.ts +17 -14
  37. package/dist/types/route-definition/helpers-types.d.ts +7 -2
  38. package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
  39. package/dist/types/route-definition/rewrite.d.ts +48 -5
  40. package/dist/types/route-types.d.ts +2 -2
  41. package/dist/types/router/handler-context.d.ts +14 -2
  42. package/dist/types/router/intercept-resolution.d.ts +19 -46
  43. package/dist/types/router/lazy-includes.d.ts +7 -0
  44. package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
  45. package/dist/types/router/middleware.d.ts +0 -16
  46. package/dist/types/router/navigation-snapshot.d.ts +2 -0
  47. package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
  48. package/dist/types/router/revalidation.d.ts +8 -1
  49. package/dist/types/router/router-context.d.ts +4 -16
  50. package/dist/types/router/router-interfaces.d.ts +0 -6
  51. package/dist/types/router/router-options.d.ts +4 -7
  52. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
  53. package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
  54. package/dist/types/router/segment-resolution.d.ts +1 -1
  55. package/dist/types/router/segment-wrappers.d.ts +3 -5
  56. package/dist/types/router/substitute-pattern-params.d.ts +19 -2
  57. package/dist/types/router/tracing.d.ts +2 -2
  58. package/dist/types/rsc/full-payload.d.ts +14 -2
  59. package/dist/types/rsc/helpers.d.ts +8 -0
  60. package/dist/types/rsc/manifest-init.d.ts +20 -0
  61. package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
  62. package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
  63. package/dist/types/rsc/rewrite-passes.d.ts +12 -2
  64. package/dist/types/rsc/server-action.d.ts +6 -0
  65. package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
  66. package/dist/types/rsc/shell-capture.d.ts +7 -0
  67. package/dist/types/rsc/types.d.ts +5 -0
  68. package/dist/types/segment-system.d.ts +15 -0
  69. package/dist/types/server/context.d.ts +64 -4
  70. package/dist/types/server/request-context.d.ts +41 -4
  71. package/dist/types/server/request-headers.d.ts +12 -0
  72. package/dist/types/server/rewrite-trail.d.ts +8 -1
  73. package/dist/types/testing/e2e/index.d.ts +1 -1
  74. package/dist/types/testing/flight.entry.d.ts +1 -1
  75. package/dist/types/testing/index.d.ts +1 -1
  76. package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
  77. package/dist/types/testing/render-route.d.ts +21 -11
  78. package/dist/types/testing/run-loader.d.ts +7 -0
  79. package/dist/types/testing/run-middleware.d.ts +7 -0
  80. package/dist/types/testing/run-revalidate.d.ts +5 -4
  81. package/dist/types/testing/serve-shell-request.d.ts +41 -4
  82. package/dist/types/testing/shell-status.d.ts +17 -0
  83. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  84. package/dist/types/testing/vitest.d.ts +10 -1
  85. package/dist/types/thenable-status.d.ts +0 -6
  86. package/dist/types/types/handler-context.d.ts +12 -1
  87. package/dist/types/types/loader-types.d.ts +4 -3
  88. package/dist/types/types/route-entry.d.ts +2 -1
  89. package/dist/types/urls/path-helper-types.d.ts +4 -2
  90. package/dist/types/urls/path-helper.d.ts +5 -0
  91. package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
  92. package/dist/types/vite/discovery/state.d.ts +20 -0
  93. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
  94. package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
  95. package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
  96. package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
  97. package/dist/vite/index.js +6816 -6547
  98. package/package.json +1 -1
  99. package/skills/app-structure/SKILL.md +255 -0
  100. package/skills/app-structure/content-site.md +333 -0
  101. package/skills/app-structure/e-commerce.md +435 -0
  102. package/skills/app-structure/large-app.md +885 -0
  103. package/skills/app-structure/multi-app.md +280 -0
  104. package/skills/breadcrumbs/SKILL.md +6 -1
  105. package/skills/caching/SKILL.md +3 -1
  106. package/skills/catalog.json +12 -0
  107. package/skills/client-urls/SKILL.md +195 -32
  108. package/skills/composability/SKILL.md +10 -8
  109. package/skills/handler-use/SKILL.md +48 -9
  110. package/skills/hooks/handle-and-actions.md +104 -0
  111. package/skills/hooks/navigation.md +24 -10
  112. package/skills/hooks/urls.md +1 -1
  113. package/skills/i18n/SKILL.md +2 -1
  114. package/skills/intercept/SKILL.md +60 -29
  115. package/skills/layout/SKILL.md +6 -3
  116. package/skills/links/SKILL.md +151 -17
  117. package/skills/loader/SKILL.md +32 -10
  118. package/skills/middleware/SKILL.md +111 -15
  119. package/skills/observability/SKILL.md +11 -0
  120. package/skills/parallel/SKILL.md +5 -5
  121. package/skills/ppr/SKILL.md +43 -8
  122. package/skills/prerender/SKILL.md +1 -2
  123. package/skills/rango/SKILL.md +3 -0
  124. package/skills/response-routes/SKILL.md +4 -1
  125. package/skills/scripts/SKILL.md +12 -1
  126. package/skills/server-actions/SKILL.md +60 -20
  127. package/skills/testing/SKILL.md +1 -0
  128. package/skills/testing/client-components.md +13 -10
  129. package/skills/testing/loader.md +25 -24
  130. package/skills/testing/middleware.md +11 -11
  131. package/skills/testing/response-routes.md +4 -1
  132. package/skills/testing/serve-shell-request.md +85 -21
  133. package/skills/ux-architecture/SKILL.md +980 -0
  134. package/skills/view-transitions/SKILL.md +43 -10
  135. package/src/__augment-tests__/augmented.check.ts +28 -0
  136. package/src/browser/event-controller.ts +44 -18
  137. package/src/browser/intercept-utils.ts +3 -2
  138. package/src/browser/merge-segment-loaders.ts +0 -85
  139. package/src/browser/navigation-bridge.ts +119 -98
  140. package/src/browser/navigation-client.ts +20 -15
  141. package/src/browser/navigation-store.ts +4 -2
  142. package/src/browser/navigation-transaction.ts +9 -8
  143. package/src/browser/partial-update.ts +90 -32
  144. package/src/browser/prefetch/cache.ts +8 -0
  145. package/src/browser/prefetch/fetch.ts +13 -3
  146. package/src/browser/rango-marker.ts +13 -0
  147. package/src/browser/react/NavigationProvider.tsx +25 -7
  148. package/src/browser/react/filter-segment-order.ts +13 -0
  149. package/src/browser/react/use-action.ts +9 -15
  150. package/src/browser/react/use-handle.ts +63 -71
  151. package/src/browser/react/use-href.tsx +24 -5
  152. package/src/browser/react/use-link-status.ts +1 -1
  153. package/src/browser/react/use-navigation.ts +30 -40
  154. package/src/browser/react/use-reverse.ts +4 -17
  155. package/src/browser/react/use-router.ts +37 -6
  156. package/src/browser/react/use-segments.ts +8 -12
  157. package/src/browser/response-adapter.ts +11 -5
  158. package/src/browser/rsc-router.tsx +3 -1
  159. package/src/browser/scroll-restoration.ts +34 -20
  160. package/src/browser/segment-reconciler.ts +11 -29
  161. package/src/browser/server-action-bridge.ts +52 -16
  162. package/src/browser/types.ts +40 -8
  163. package/src/build/generate-manifest.ts +12 -1
  164. package/src/build/route-types/codegen.ts +6 -2
  165. package/src/build/route-types/per-module-writer.ts +94 -61
  166. package/src/cache/cache-runtime.ts +9 -0
  167. package/src/cache/cache-scope.ts +2 -1
  168. package/src/cache/cf/cf-cache-store.ts +2 -0
  169. package/src/cache/cf/cf-shell-frame.ts +3 -0
  170. package/src/cache/types.ts +8 -0
  171. package/src/cache/vercel/vercel-cache-store.ts +6 -1
  172. package/src/client-urls/client-root.tsx +10 -5
  173. package/src/client-urls/client-urls.ts +246 -166
  174. package/src/client-urls/navigation.ts +45 -38
  175. package/src/client-urls/revalidate-registry.ts +12 -3
  176. package/src/client-urls/server-projection.ts +286 -75
  177. package/src/client-urls/types.ts +40 -16
  178. package/src/client.rsc.tsx +11 -1
  179. package/src/client.tsx +9 -1
  180. package/src/decode-loader-results.ts +64 -31
  181. package/src/href-client.ts +3 -1
  182. package/src/index.rsc.ts +1 -0
  183. package/src/index.ts +12 -4
  184. package/src/loader-redirect.tsx +2 -3
  185. package/src/outlet-context.ts +7 -6
  186. package/src/prefetch-markers.ts +15 -5
  187. package/src/reverse.ts +3 -3
  188. package/src/route-content-wrapper.tsx +19 -17
  189. package/src/route-definition/dsl-helpers.ts +27 -7
  190. package/src/route-definition/helpers-types.ts +7 -2
  191. package/src/route-definition/resolve-handler-use.ts +23 -10
  192. package/src/route-definition/rewrite.ts +86 -19
  193. package/src/route-types.ts +1 -2
  194. package/src/router/find-match.ts +37 -1
  195. package/src/router/handler-context.ts +85 -40
  196. package/src/router/intercept-resolution.ts +161 -307
  197. package/src/router/lazy-includes.ts +43 -27
  198. package/src/router/loader-resolution.ts +21 -1
  199. package/src/router/manifest.ts +6 -8
  200. package/src/router/match-api.ts +11 -1
  201. package/src/router/match-middleware/background-revalidation.ts +2 -4
  202. package/src/router/match-middleware/cache-lookup.ts +16 -20
  203. package/src/router/match-middleware/intercept-resolution.ts +41 -60
  204. package/src/router/match-result.ts +31 -2
  205. package/src/router/middleware.ts +6 -147
  206. package/src/router/navigation-snapshot.ts +38 -2
  207. package/src/router/prefetch-cache-ttl.ts +10 -18
  208. package/src/router/prerender-match.ts +2 -1
  209. package/src/router/revalidation.ts +24 -3
  210. package/src/router/router-context.ts +5 -18
  211. package/src/router/router-interfaces.ts +0 -7
  212. package/src/router/router-options.ts +4 -7
  213. package/src/router/segment-resolution/fresh.ts +6 -2
  214. package/src/router/segment-resolution/helpers.ts +7 -1
  215. package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
  216. package/src/router/segment-resolution/revalidation.ts +75 -35
  217. package/src/router/segment-resolution.ts +0 -1
  218. package/src/router/segment-wrappers.ts +8 -13
  219. package/src/router/substitute-pattern-params.ts +37 -2
  220. package/src/router/tracing.ts +2 -2
  221. package/src/router.ts +2 -9
  222. package/src/rsc/full-payload.ts +26 -4
  223. package/src/rsc/handler.ts +83 -41
  224. package/src/rsc/helpers.ts +13 -0
  225. package/src/rsc/manifest-init.ts +56 -3
  226. package/src/rsc/prefetch-render-error.ts +38 -0
  227. package/src/rsc/progressive-enhancement.ts +161 -211
  228. package/src/rsc/rewrite-passes.ts +56 -9
  229. package/src/rsc/rsc-rendering.ts +79 -23
  230. package/src/rsc/server-action.ts +25 -2
  231. package/src/rsc/shell-capture-constants.ts +22 -0
  232. package/src/rsc/shell-capture.ts +11 -0
  233. package/src/rsc/types.ts +5 -0
  234. package/src/segment-system.tsx +125 -119
  235. package/src/server/context.ts +96 -5
  236. package/src/server/handle-store.ts +3 -2
  237. package/src/server/request-context.ts +42 -3
  238. package/src/server/request-headers.ts +32 -0
  239. package/src/server/rewrite-trail.ts +55 -9
  240. package/src/ssr/ssr-root.tsx +6 -10
  241. package/src/testing/dispatch.ts +4 -5
  242. package/src/testing/e2e/index.ts +1 -0
  243. package/src/testing/flight.entry.ts +1 -0
  244. package/src/testing/index.ts +1 -0
  245. package/src/testing/internal/client-urls-projection.ts +31 -11
  246. package/src/testing/render-route.tsx +83 -40
  247. package/src/testing/run-loader.ts +26 -2
  248. package/src/testing/run-middleware.ts +10 -0
  249. package/src/testing/run-revalidate.ts +13 -6
  250. package/src/testing/serve-shell-request.ts +77 -4
  251. package/src/testing/shell-status.ts +40 -0
  252. package/src/testing/vitest-stubs/version.ts +1 -0
  253. package/src/testing/vitest.ts +74 -7
  254. package/src/thenable-status.ts +0 -11
  255. package/src/types/handler-context.ts +12 -1
  256. package/src/types/loader-types.ts +4 -3
  257. package/src/types/route-entry.ts +2 -1
  258. package/src/urls/include-helper.ts +38 -11
  259. package/src/urls/path-helper-types.ts +4 -2
  260. package/src/urls/path-helper.ts +16 -3
  261. package/src/vite/discovery/client-urls-projection.ts +205 -16
  262. package/src/vite/discovery/discover-routers.ts +2 -2
  263. package/src/vite/discovery/prerender-collection.ts +2 -6
  264. package/src/vite/discovery/state.ts +26 -0
  265. package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
  266. package/src/vite/plugins/expose-internal-ids.ts +46 -40
  267. package/src/vite/plugins/hoist/source.ts +8 -7
  268. package/src/vite/plugins/transition-when-hoist.ts +1 -0
  269. package/src/vite/plugins/version-plugin.ts +7 -0
  270. package/src/vite/plugins/version.d.ts +7 -0
  271. package/src/vite/plugins/virtual-entries.ts +20 -0
  272. package/src/vite/rango.ts +4 -1
  273. package/src/vite/router-discovery.ts +39 -7
  274. package/src/vite/utils/prerender-utils.ts +17 -0
@@ -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,
@@ -973,6 +976,32 @@ export function createRSCHandler<
973
976
  // "middleware-short-circuit").
974
977
  getRequestContext()._requestMode = plan.mode;
975
978
 
979
+ // A rewritten pass of an action must reach the branch that runs it: the
980
+ // one that ran it re-renders from its outcome, and a JS action a global
981
+ // rewrite moved before it ran runs only on the action branch. Any other
982
+ // plan would serve the POST as a fresh request or drop the action; a
983
+ // reload (version mismatch, app switch) does neither.
984
+ const passCtx = getRequestContext();
985
+ const actionRan = passCtx._actionRan;
986
+ const actionMode = actionRan
987
+ ? actionRan.kind === "action"
988
+ ? "action"
989
+ : "pe-render"
990
+ : url.searchParams.has("_rsc_action") && rewriteTrail(passCtx)
991
+ ? "action"
992
+ : undefined;
993
+ if (
994
+ actionMode &&
995
+ plan.mode !== actionMode &&
996
+ plan.mode !== "version-mismatch" &&
997
+ plan.mode !== "app-switch"
998
+ ) {
999
+ throw new Error(
1000
+ `A rewrite of a server action or form post must target a page ` +
1001
+ `route; ${url.pathname} ${plan.mode === "response" ? "is a response route" : plan.mode === "redirect" ? "redirects" : "is not one"}.`,
1002
+ );
1003
+ }
1004
+
976
1005
  // ---- 2. Terminal plans (no execution needed) ----
977
1006
  if (plan.mode === "redirect") {
978
1007
  // Redirects are handled by the pipeline (match/matchPartial),
@@ -1224,51 +1253,64 @@ export function createRSCHandler<
1224
1253
 
1225
1254
  // ---- Action: execute action, then revalidate wrapped in route middleware ----
1226
1255
  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,
1256
+ const reqCtx = getRequestContext();
1257
+ const ran = reqCtx._actionRan;
1258
+ let actionContinuation: ActionContinuation;
1259
+ if (ran?.kind === "action") {
1260
+ // A rewrite pass after the action ran: never run it again.
1261
+ actionContinuation = await continuationForRewritePass(
1262
+ handlerCtx,
1263
+ request,
1264
+ env,
1265
+ ran.continuation,
1266
+ );
1267
+ } else {
1268
+ try {
1269
+ // Instrument the action execution as its own phase (action:<actionId> +
1270
+ // rango.action), so a POST shows the mutation time AND which action ran,
1271
+ // not just the downstream revalidation render. The action's own
1272
+ // loaders/fetches nest under rango.action.
1273
+ const actionOutcome = await withTimeout(
1274
+ observePhase(PHASES.action(plan.actionId), () =>
1275
+ executeServerAction(
1276
+ handlerCtx,
1277
+ request,
1278
+ env,
1279
+ url,
1280
+ plan.actionId,
1281
+ handleStore,
1282
+ ),
1283
+ ),
1284
+ router.timeouts.actionMs,
1285
+ "action",
1286
+ );
1287
+ if (actionOutcome.timedOut) {
1288
+ return handleTimeoutResponse(
1237
1289
  request,
1238
1290
  env,
1239
1291
  url,
1292
+ "action",
1293
+ actionOutcome.durationMs,
1294
+ plan.route.routeKey,
1240
1295
  plan.actionId,
1241
- handleStore,
1242
- ),
1243
- ),
1244
- router.timeouts.actionMs,
1245
- "action",
1246
- );
1247
- if (actionOutcome.timedOut) {
1248
- return handleTimeoutResponse(
1296
+ );
1297
+ }
1298
+ const result = actionOutcome.result;
1299
+ // Response means redirect or error boundary — done.
1300
+ if (result instanceof Response) return result;
1301
+ actionContinuation = result;
1302
+ reqCtx._actionRan = { kind: "action", continuation: result };
1303
+ } catch (error) {
1304
+ callOnError(error, "action", {
1249
1305
  request,
1250
- env,
1251
1306
  url,
1252
- "action",
1253
- actionOutcome.durationMs,
1254
- plan.route.routeKey,
1255
- plan.actionId,
1256
- );
1307
+ env,
1308
+ actionId: plan.actionId,
1309
+ handledByBoundary: false,
1310
+ });
1311
+ console.error(`[RSC] Action error:`, error);
1312
+ throw error;
1257
1313
  }
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
1314
  }
1273
1315
 
1274
1316
  // Revalidation render wrapped in route middleware.
@@ -1423,7 +1465,7 @@ export function createRSCHandler<
1423
1465
 
1424
1466
  const payload: RscPayload = {
1425
1467
  metadata: {
1426
- pathname: renderedLocation(getRequestContext(), url).pathname,
1468
+ ...payloadLocation(getRequestContext(), url),
1427
1469
  routerId: router.id,
1428
1470
  basename: router.basename,
1429
1471
  segments: [notFoundSegment],
@@ -1527,7 +1569,7 @@ export function createRSCHandler<
1527
1569
  if (routeMiddleware.length > 0) {
1528
1570
  const mwResponse = await executeMiddleware(
1529
1571
  buildRouteMiddlewareEntries<TEnv>(routeMiddleware),
1530
- request,
1572
+ actionContinuation ? actionRenderRequest(request) : request,
1531
1573
  env,
1532
1574
  variables,
1533
1575
  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
+ }