@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -8,12 +8,11 @@
8
8
  */
9
9
 
10
10
  import { createElement } from "react";
11
- import { RouteNotFoundError } from "../errors.js";
11
+ import { isRouteNotFoundError } from "../errors.js";
12
12
  import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
13
13
  import {
14
14
  runWithRequestContext,
15
15
  setRequestContextParams,
16
- requireRequestContext,
17
16
  getRequestContext,
18
17
  _getRequestContext,
19
18
  createRequestContext,
@@ -31,6 +30,12 @@ import {
31
30
  interceptRedirectForPartial,
32
31
  buildRouteMiddlewareEntries,
33
32
  } from "./helpers.js";
33
+ import { guardOutgoingRedirect } from "./redirect-guard.js";
34
+ import { resolvedHandleStream } from "../handles/deferred-resolution.js";
35
+ import {
36
+ isWebSocketUpgradeResponse,
37
+ appendVaryAccept,
38
+ } from "../response-utils.js";
34
39
  import {
35
40
  handleResponseRoute,
36
41
  type ResponseRouteMatch,
@@ -44,8 +49,6 @@ import {
44
49
  createReverseFunction,
45
50
  stripInternalParams,
46
51
  } from "../router/handler-context.js";
47
- import { getRouterContext } from "../router/router-context.js";
48
- import { resolveSink, safeEmit } from "../router/telemetry.js";
49
52
  import { contextSet } from "../context-var.js";
50
53
  import {
51
54
  hasCachedManifest,
@@ -56,6 +59,8 @@ import {
56
59
  getRouterTrie,
57
60
  } from "../route-map-builder.js";
58
61
  import type { HandlerContext } from "./handler-context.js";
62
+ import type { CacheErrorCategory } from "../cache/cache-error.js";
63
+ import type { SegmentCacheStore } from "../cache/types.js";
59
64
  import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
60
65
  import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
61
66
  import {
@@ -64,7 +69,10 @@ import {
64
69
  type ActionContinuation,
65
70
  } from "./server-action.js";
66
71
  import { handleLoaderFetch } from "./loader-fetch.js";
67
- import { checkRequestOrigin, type OriginCheckPhase } from "./origin-guard.js";
72
+ import {
73
+ checkRequestOrigin,
74
+ ORIGIN_CHECK_PHASE_BY_MODE,
75
+ } from "./origin-guard.js";
68
76
  import { handleRscRendering } from "./rsc-rendering.js";
69
77
  import {
70
78
  withTimeout,
@@ -77,10 +85,13 @@ import {
77
85
  appendMetric,
78
86
  buildMetricsTiming,
79
87
  } from "../router/metrics.js";
88
+ import { observePhase, PHASES } from "../router/instrument.js";
89
+ import { safeEmit, resolveSink, getRequestId } from "../router/telemetry.js";
80
90
  import {
81
91
  startSSRSetup,
82
92
  getSSRSetup,
83
93
  mayNeedSSR,
94
+ isRscRequest,
84
95
  SSR_SETUP_VAR,
85
96
  } from "./ssr-setup.js";
86
97
  import {
@@ -122,12 +133,35 @@ import {
122
133
  * });
123
134
  * ```
124
135
  */
136
+
137
+ /**
138
+ * Response that tells the client to do a full document navigation. Shared by
139
+ * the terminal reload plans (version-mismatch and app-switch): an empty 200
140
+ * carrying X-RSC-Reload, which the client turns into window.location.href.
141
+ */
142
+ function createReloadResponse(reloadUrl: string) {
143
+ return createResponseWithMergedHeaders(null, {
144
+ status: 200,
145
+ headers: {
146
+ "X-RSC-Reload": reloadUrl,
147
+ "content-type": "text/x-component;charset=utf-8",
148
+ },
149
+ });
150
+ }
151
+
125
152
  export function createRSCHandler<
126
153
  TEnv = unknown,
127
154
  TRoutes extends Record<string, string> = Record<string, string>,
128
155
  >(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
129
156
  const { router, version = VERSION, nonce: nonceProvider } = options;
130
157
 
158
+ // Handler-owned registry of explicit per-scope stores from cache({ store }).
159
+ // Lives in the closure so it is scoped per handler (multi-router deployments
160
+ // get separate registries) and accumulates every explicit store this handler
161
+ // resolves across requests. updateTag()/revalidateTag() iterate it to reach
162
+ // stores not covered by the app-level ctx._cacheStore.
163
+ const explicitTaggedStores = new Set<SegmentCacheStore>();
164
+
131
165
  // Use provided deps or default to @vitejs/plugin-rsc/rsc exports
132
166
  const deps = options.deps ?? rscDeps;
133
167
  const {
@@ -213,23 +247,18 @@ export function createRSCHandler<
213
247
  metadata: { timeout: true, phase, durationMs },
214
248
  });
215
249
 
216
- try {
217
- const routerCtx = getRouterContext();
218
- if (routerCtx?.telemetry) {
219
- safeEmit(resolveSink(routerCtx.telemetry), {
220
- type: "request.timeout" as const,
221
- timestamp: performance.now(),
222
- requestId: routerCtx.requestId,
223
- phase,
224
- pathname: url.pathname,
225
- routeKey,
226
- actionId,
227
- durationMs,
228
- customHandler: !!router.onTimeout,
229
- });
230
- }
231
- } catch {
232
- // Router context may not be available
250
+ if (router.telemetry) {
251
+ safeEmit(resolveSink(router.telemetry), {
252
+ type: "request.timeout",
253
+ timestamp: performance.now(),
254
+ requestId: getRequestId(request),
255
+ phase,
256
+ pathname: url.pathname,
257
+ routeKey,
258
+ actionId,
259
+ durationMs,
260
+ customHandler: !!router.onTimeout,
261
+ });
233
262
  }
234
263
 
235
264
  if (router.onTimeout) {
@@ -262,16 +291,27 @@ export function createRSCHandler<
262
291
  function createRedirectFlightResponse(
263
292
  redirectUrl: string,
264
293
  locationState?: Record<string, unknown>,
294
+ external?: boolean,
265
295
  ): Response {
266
296
  const redirectPayload: RscPayload = {
267
297
  metadata: {
268
298
  pathname: redirectUrl,
269
299
  segments: [],
270
- redirect: { url: redirectUrl },
300
+ redirect: { url: redirectUrl, ...(external && { external: true }) },
271
301
  ...(locationState && { locationState }),
272
302
  },
273
303
  };
274
- const rscStream = renderToReadableStream<RscPayload>(redirectPayload);
304
+ const rscStream = renderToReadableStream<RscPayload>(redirectPayload, {
305
+ onError: (error: unknown) => {
306
+ const reqCtx = _getRequestContext<TEnv>();
307
+ if (!reqCtx) return;
308
+ callOnError(error, "rendering", {
309
+ request: reqCtx.request,
310
+ url: reqCtx.url,
311
+ env: reqCtx.env,
312
+ });
313
+ },
314
+ });
275
315
  return createResponseWithMergedHeaders(rscStream, {
276
316
  status: 200,
277
317
  headers: { "content-type": "text/x-component;charset=utf-8" },
@@ -352,7 +392,7 @@ export function createRSCHandler<
352
392
  // Resolve cache store configuration
353
393
  // Priority: options.cache (handler override) > router.cache (router default)
354
394
  // Store is enabled only if: config provided, enabled, and no ?__no_cache query param
355
- let cacheStore = undefined;
395
+ let cacheStore: SegmentCacheStore | undefined;
356
396
  const cacheOption = options.cache ?? router.cache;
357
397
  if (cacheOption && !url.searchParams.has("__no_cache")) {
358
398
  const cacheConfig =
@@ -419,10 +459,18 @@ export function createRSCHandler<
419
459
  url,
420
460
  variables,
421
461
  cacheStore,
462
+ explicitTaggedStores,
422
463
  cacheProfiles: router.cacheProfiles,
423
464
  executionContext: executionCtx,
424
465
  themeConfig: router.themeConfig,
466
+ stateCookieName: router.resolvedStateCookieName,
467
+ version,
425
468
  });
469
+ // Thread the true request entry timestamp onto the context so a metrics
470
+ // store created MID-request (ctx.debugPerformance() / getMetricsStore) anchors
471
+ // to the real start, not the opt-in moment — phases that began earlier then
472
+ // report non-negative offsets. Set unconditionally: debug may be enabled later.
473
+ requestContext._handlerStart = handlerStart;
426
474
  if (earlyMetricsStore) {
427
475
  requestContext._debugPerformance = true;
428
476
  requestContext._metricsStore = earlyMetricsStore;
@@ -431,7 +479,7 @@ export function createRSCHandler<
431
479
  // can surface non-fatal errors through the router's onError callback.
432
480
  requestContext._reportBackgroundError = (
433
481
  error: unknown,
434
- category: string,
482
+ category: CacheErrorCategory,
435
483
  ) => {
436
484
  callOnError(error, "cache", {
437
485
  request,
@@ -464,79 +512,112 @@ export function createRSCHandler<
464
512
  // Store basename on request context (scoped per-request via existing ALS)
465
513
  requestContext._basename = router.basename;
466
514
 
467
- return runWithRequestContext(requestContext, async () => {
468
- // Core handler logic (wrapped by middleware)
469
- const coreHandler = async (): Promise<Response> => {
470
- return coreRequestHandler(request, env, url, variables, nonce);
471
- };
472
-
473
- // Execute middleware chain if any, otherwise call core handler directly
474
- let response: Response;
475
- if (matchedMiddleware.length > 0) {
476
- const mwResponse = await executeMiddleware(
477
- matchedMiddleware,
478
- request,
479
- env,
480
- variables,
481
- coreHandler,
482
- createReverseFunction(getRequiredRouteMap()),
483
- );
515
+ // Resolved span tracing for this request (read at each traced phase).
516
+ requestContext._tracing = router.tracing;
517
+
518
+ // The "rango.request" span is opened inside the request context so the
519
+ // Cloudflare runner can read executionContext.tracing, and so every nested
520
+ // phase span (and the platform's automatic KV/D1/fetch spans) nests under
521
+ // it. Construction-bound: the span ends when the Response is built, never
522
+ // wrapping the streamed body. metric:false — handler:total is metered
523
+ // directly below (a grand total incl. the pre-context bootstrap timings).
524
+ // When tracing is off this is a direct pass-through.
525
+ return runWithRequestContext(requestContext, () =>
526
+ observePhase(PHASES.request, async (span) => {
527
+ span.setAttribute("http.method", request.method);
528
+ // The matched route template is not known until match() runs later, so
529
+ // emit the concrete path as url.path (low-level), NOT http.route — the
530
+ // latter is reserved for the low-cardinality template (OTel convention).
531
+ span.setAttribute("url.path", url.pathname);
532
+
533
+ // Core handler logic (wrapped by middleware)
534
+ const coreHandler = async (): Promise<Response> => {
535
+ return coreRequestHandler(request, env, url, variables, nonce);
536
+ };
484
537
 
485
- if (
486
- url.searchParams.has("_rsc_partial") ||
487
- url.searchParams.has("_rsc_action")
488
- ) {
489
- const intercepted = interceptRedirectForPartial(
490
- mwResponse,
491
- createRedirectFlightResponse,
538
+ // Execute middleware chain if any, otherwise call core handler directly
539
+ let response: Response;
540
+ if (matchedMiddleware.length > 0) {
541
+ const mwResponse = await executeMiddleware(
542
+ matchedMiddleware,
543
+ request,
544
+ env,
545
+ variables,
546
+ coreHandler,
547
+ createReverseFunction(getRequiredRouteMap()),
492
548
  );
493
- response = intercepted ?? finalizeResponse(mwResponse);
549
+
550
+ if (
551
+ url.searchParams.has("_rsc_partial") ||
552
+ url.searchParams.has("_rsc_action")
553
+ ) {
554
+ const intercepted = interceptRedirectForPartial(
555
+ mwResponse,
556
+ createRedirectFlightResponse,
557
+ );
558
+ response = intercepted ?? finalizeResponse(mwResponse);
559
+ } else {
560
+ response = finalizeResponse(mwResponse);
561
+ }
494
562
  } else {
495
- response = finalizeResponse(mwResponse);
563
+ response = await coreHandler();
496
564
  }
497
- } else {
498
- response = await coreHandler();
499
- }
500
565
 
501
- // Finalize metrics after all middleware (including post-next work)
502
- // has completed so :post spans are captured in the timeline.
503
- // Handler timing parts are always emitted (even without debug metrics)
504
- // so non-debug requests still get bootstrap Server-Timing entries.
505
- const handlerTimingArr: string[] = variables.__handlerTiming || [];
506
- // Preserve any existing Server-Timing set by response routes or middleware
507
- const existingTiming = response.headers.get("Server-Timing");
508
- const timingParts = existingTiming
509
- ? [existingTiming, ...handlerTimingArr]
510
- : [...handlerTimingArr];
511
-
512
- const metricsStore = requestContext._metricsStore;
513
- if (metricsStore) {
514
- // When the store was created at handler start (earlyMetricsStore),
515
- // handler:total covers the full request. When ctx.debugPerformance()
516
- // created the store mid-request, use its requestStart to avoid a
517
- // negative startTime offset.
518
- const totalStart = earlyMetricsStore
519
- ? handlerStart
520
- : metricsStore.requestStart;
521
- appendMetric(
522
- metricsStore,
523
- "handler:total",
524
- totalStart,
525
- performance.now() - totalStart,
526
- );
527
- const metricsTiming = buildMetricsTiming(
528
- request.method,
529
- url.pathname,
530
- metricsStore,
531
- );
532
- if (metricsTiming) timingParts.push(metricsTiming);
533
- }
566
+ // Finalize metrics after all middleware (including post-next work)
567
+ // has completed so :post spans are captured in the timeline.
568
+ // Handler timing parts are always emitted (even without debug metrics)
569
+ // so non-debug requests still get bootstrap Server-Timing entries.
570
+ const handlerTimingArr: string[] = variables.__handlerTiming || [];
571
+ // Preserve any existing Server-Timing set by response routes or middleware
572
+ const existingTiming = response.headers.get("Server-Timing");
573
+ const timingParts = existingTiming
574
+ ? [existingTiming, ...handlerTimingArr]
575
+ : [...handlerTimingArr];
576
+
577
+ const metricsStore = requestContext._metricsStore;
578
+ if (metricsStore) {
579
+ // When the store was created at handler start (earlyMetricsStore),
580
+ // handler:total covers the full request. When ctx.debugPerformance()
581
+ // created the store mid-request its requestStart is now the threaded
582
+ // _handlerStart (== handlerStart), so both branches yield the true
583
+ // request entry; reading the store's own anchor keeps this correct even
584
+ // if a store ever lands without the threading (falls back to its start).
585
+ const totalStart = earlyMetricsStore
586
+ ? handlerStart
587
+ : metricsStore.requestStart;
588
+ appendMetric(
589
+ metricsStore,
590
+ "handler:total",
591
+ totalStart,
592
+ performance.now() - totalStart,
593
+ );
594
+ const metricsTiming = buildMetricsTiming(
595
+ request.method,
596
+ url.pathname,
597
+ metricsStore,
598
+ );
599
+ if (metricsTiming) timingParts.push(metricsTiming);
600
+ }
534
601
 
535
- const fullTiming = timingParts.join(", ");
536
- if (fullTiming) response.headers.set("Server-Timing", fullTiming);
602
+ const fullTiming = timingParts.join(", ");
603
+ if (fullTiming && !isWebSocketUpgradeResponse(response)) {
604
+ try {
605
+ response.headers.set("Server-Timing", fullTiming);
606
+ } catch {
607
+ // Immutable headers (e.g. a passed-through platform Response) — drop
608
+ // the timing header, never the response. Instrumentation must not
609
+ // 500 a request.
610
+ }
611
+ }
537
612
 
538
- return response;
539
- });
613
+ // Single open-redirect chokepoint: every response (PE, full-page,
614
+ // middleware short-circuit, response-route) funnels through here, so
615
+ // guarding browser-followed (3xx) redirects once covers them all and any
616
+ // future redirect exit. Soft SPA/Flight redirects are 200/204 and pass
617
+ // through untouched (validated client-side instead).
618
+ return guardOutgoingRedirect(response, url.origin, router.basename);
619
+ }),
620
+ );
540
621
  };
541
622
 
542
623
  // Core request handling logic (separated for middleware wrapping).
@@ -593,10 +674,7 @@ export function createRSCHandler<
593
674
  routerId: router.id,
594
675
  });
595
676
  } catch (error) {
596
- if (
597
- error instanceof RouteNotFoundError ||
598
- (error instanceof Error && error.name === "RouteNotFoundError")
599
- ) {
677
+ if (isRouteNotFoundError(error)) {
600
678
  // Let the render path handle 404 — match()/matchPartial() will
601
679
  // re-throw RouteNotFoundError and the catch block in
602
680
  // executeRenderWithMiddleware renders the not-found page.
@@ -637,24 +715,18 @@ export function createRSCHandler<
637
715
  console.log(
638
716
  `[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
639
717
  );
640
- return createResponseWithMergedHeaders(null, {
641
- status: 200,
642
- headers: {
643
- "X-RSC-Reload": plan.reloadUrl,
644
- "content-type": "text/x-component;charset=utf-8",
645
- },
646
- });
718
+ return createReloadResponse(plan.reloadUrl);
719
+ }
720
+
721
+ if (plan.mode === "app-switch") {
722
+ // Cross-app SPA navigation crossed a host-router app boundary. Force a
723
+ // real document navigation so the target app's document is re-established
724
+ // (stylesheets, theme, warmup, prefetch-TTL). See request-classification.
725
+ return createReloadResponse(plan.reloadUrl);
647
726
  }
648
727
 
649
728
  // ---- 3. Origin guard (gate for action/loader/PE modes) ----
650
- const originPhase: OriginCheckPhase | null =
651
- plan.mode === "action"
652
- ? "action"
653
- : plan.mode === "loader"
654
- ? "loader"
655
- : plan.mode === "pe-render"
656
- ? "pe-form"
657
- : null;
729
+ const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
658
730
  if (originPhase) {
659
731
  const originResult = await checkRequestOrigin(
660
732
  request,
@@ -682,22 +754,17 @@ export function createRSCHandler<
682
754
  },
683
755
  });
684
756
 
685
- try {
686
- const routerCtx = getRouterContext();
687
- if (routerCtx?.telemetry) {
688
- safeEmit(resolveSink(routerCtx.telemetry), {
689
- type: "request.origin-rejected" as const,
690
- timestamp: performance.now(),
691
- requestId: routerCtx.requestId,
692
- method: request.method,
693
- pathname: url.pathname,
694
- phase: originPhase,
695
- origin: request.headers.get("origin"),
696
- host: request.headers.get("host"),
697
- });
698
- }
699
- } catch {
700
- // Router context may not be available
757
+ if (router.telemetry) {
758
+ safeEmit(resolveSink(router.telemetry), {
759
+ type: "request.origin-rejected",
760
+ timestamp: performance.now(),
761
+ requestId: getRequestId(request),
762
+ method: request.method,
763
+ pathname: url.pathname,
764
+ phase: originPhase,
765
+ origin: request.headers.get("origin"),
766
+ host: request.headers.get("host"),
767
+ });
701
768
  }
702
769
 
703
770
  return originResult;
@@ -728,11 +795,11 @@ export function createRSCHandler<
728
795
  nonce: string | undefined,
729
796
  ): Promise<Response> {
730
797
  // Common setup
731
- const handleStore = requireRequestContext()._handleStore;
798
+ const handleStore = getRequestContext()._handleStore;
732
799
 
733
800
  // Wire up error reporting for late streaming-handle failures
734
801
  handleStore.onError = (error: Error) => {
735
- const reqCtx = requireRequestContext();
802
+ const reqCtx = getRequestContext();
736
803
  callOnError(error, "handler", {
737
804
  request,
738
805
  url,
@@ -740,22 +807,17 @@ export function createRSCHandler<
740
807
  params: reqCtx.params as Record<string, string>,
741
808
  handledByBoundary: true,
742
809
  });
743
- try {
744
- const routerCtx = getRouterContext();
745
- if (routerCtx?.telemetry) {
746
- safeEmit(resolveSink(routerCtx.telemetry), {
747
- type: "handler.error" as const,
748
- timestamp: performance.now(),
749
- requestId: routerCtx.requestId,
750
- error,
751
- handledByBoundary: true,
752
- pathname: url.pathname,
753
- routeKey: reqCtx._routeName,
754
- params: reqCtx.params as Record<string, string>,
755
- });
756
- }
757
- } catch {
758
- // Router context may not be available (e.g. prerender path)
810
+ if (router.telemetry) {
811
+ safeEmit(resolveSink(router.telemetry), {
812
+ type: "handler.error",
813
+ timestamp: performance.now(),
814
+ requestId: getRequestId(request),
815
+ error,
816
+ handledByBoundary: true,
817
+ pathname: url.pathname,
818
+ routeKey: reqCtx._routeName,
819
+ params: reqCtx.params as Record<string, string>,
820
+ });
759
821
  }
760
822
  };
761
823
 
@@ -764,7 +826,7 @@ export function createRSCHandler<
764
826
  // instead of calling resolveRoute again.
765
827
  if (plan.mode !== "redirect") {
766
828
  setRequestContextParams(plan.route.params, plan.route.routeKey);
767
- requireRequestContext()._classifiedRoute = plan.route;
829
+ getRequestContext()._classifiedRoute = plan.route;
768
830
  }
769
831
 
770
832
  const routeReverse = createReverseFunction(getRequiredRouteMap());
@@ -804,8 +866,10 @@ export function createRSCHandler<
804
866
  );
805
867
  }
806
868
  const response = responseOutcome.result;
807
- if (plan.negotiated) {
808
- response.headers.append("Vary", "Accept");
869
+ if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
870
+ // handleResponseRoute (callHandlerWithVary) already appends Vary: Accept
871
+ // for negotiated responses; dedup so we don't emit Vary: Accept, Accept.
872
+ appendVaryAccept(response);
809
873
  }
810
874
  return response;
811
875
  }
@@ -813,14 +877,28 @@ export function createRSCHandler<
813
877
  // SSR setup: kick off in parallel for modes that need HTML rendering.
814
878
  // Placed after response-route short-circuit so response/mime routes
815
879
  // never pay for SSR work.
816
- if (plan.mode !== "loader" && mayNeedSSR(request, url)) {
880
+ //
881
+ // Only kick off when the request will actually render HTML, so the
882
+ // eager loadSSRModule() + user resolveStreaming() are not started (and
883
+ // never consumed) for a request that returns an RSC stream — that wasted
884
+ // work also leaves an orphaned Promise.all that can reject (D7). PE form
885
+ // submissions always render HTML (handleProgressiveEnhancement renders via
886
+ // getSSRSetup regardless of Accept). For full/partial-render and action,
887
+ // the render-time HTML decision is exactly !isRscRequest — mayNeedSSR is
888
+ // the coarse transport pre-filter, isRscRequest is the precise Accept call
889
+ // (it, unlike mayNeedSSR, treats a MISSING Accept as RSC). Both must pass.
890
+ const willRenderHtml =
891
+ plan.mode === "pe-render" ||
892
+ (mayNeedSSR(request, url) &&
893
+ !isRscRequest(request, url, plan.mode === "partial-render"));
894
+ if (plan.mode !== "loader" && willRenderHtml) {
817
895
  variables[SSR_SETUP_VAR] = startSSRSetup(
818
896
  handlerCtx,
819
897
  request,
820
898
  env,
821
899
  url,
822
900
  router.debugPerformance
823
- ? () => requireRequestContext()._metricsStore
901
+ ? () => getRequestContext()._metricsStore
824
902
  : undefined,
825
903
  );
826
904
  }
@@ -861,14 +939,20 @@ export function createRSCHandler<
861
939
  if (plan.mode === "action") {
862
940
  let actionContinuation: ActionContinuation | undefined;
863
941
  try {
942
+ // Instrument the action execution as its own phase (action:<actionId> +
943
+ // rango.action), so a POST shows the mutation time AND which action ran,
944
+ // not just the downstream revalidation render. The action's own
945
+ // loaders/fetches nest under rango.action.
864
946
  const actionOutcome = await withTimeout(
865
- executeServerAction(
866
- handlerCtx,
867
- request,
868
- env,
869
- url,
870
- plan.actionId,
871
- handleStore,
947
+ observePhase(PHASES.action(plan.actionId), () =>
948
+ executeServerAction(
949
+ handlerCtx,
950
+ request,
951
+ env,
952
+ url,
953
+ plan.actionId,
954
+ handleStore,
955
+ ),
872
956
  ),
873
957
  router.timeouts.actionMs,
874
958
  "action",
@@ -921,47 +1005,17 @@ export function createRSCHandler<
921
1005
  );
922
1006
  }
923
1007
 
924
- // ---- Full render / Partial render (or PE that fell through) ----
925
- if (plan.mode === "full-render" || plan.mode === "partial-render") {
926
- const isPartial = plan.mode === "partial-render";
927
- return executeRenderWithMiddleware(
928
- plan.route.routeMiddleware,
929
- plan.negotiated,
930
- plan.route.routeKey,
931
- routeReverse,
932
- request,
933
- env,
934
- url,
935
- variables,
936
- nonce,
937
- handleStore,
938
- isPartial,
939
- );
940
- }
941
-
942
- // PE that fell through (handleProgressiveEnhancement returned null)
943
- // falls back to full render
944
- if (plan.mode === "pe-render") {
945
- return executeRenderWithMiddleware(
946
- plan.route.routeMiddleware,
947
- false,
948
- plan.route.routeKey,
949
- routeReverse,
950
- request,
951
- env,
952
- url,
953
- variables,
954
- nonce,
955
- handleStore,
956
- false,
957
- );
958
- }
959
-
960
- // Redirect plan that wasn't handled above (full-page redirect — let
961
- // the pipeline handle it via match() which returns { redirect: url })
1008
+ // Full render, partial render, fallen-through PE, and full-page redirect all
1009
+ // render through the same middleware-wrapped path. Only full/partial-render
1010
+ // carry negotiation + the partial flag; pe/redirect render plainly.
1011
+ const isPartial = plan.mode === "partial-render";
1012
+ const negotiated =
1013
+ plan.mode === "full-render" || plan.mode === "partial-render"
1014
+ ? plan.negotiated
1015
+ : false;
962
1016
  return executeRenderWithMiddleware(
963
1017
  plan.route.routeMiddleware,
964
- false,
1018
+ negotiated,
965
1019
  plan.route.routeKey,
966
1020
  routeReverse,
967
1021
  request,
@@ -970,7 +1024,7 @@ export function createRSCHandler<
970
1024
  variables,
971
1025
  nonce,
972
1026
  handleStore,
973
- false,
1027
+ isPartial,
974
1028
  );
975
1029
  }
976
1030
 
@@ -987,7 +1041,7 @@ export function createRSCHandler<
987
1041
  url: URL,
988
1042
  variables: Record<string, any>,
989
1043
  nonce: string | undefined,
990
- handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
1044
+ handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
991
1045
  isPartial: boolean,
992
1046
  actionContinuation?: ActionContinuation,
993
1047
  ): Promise<Response> {
@@ -1014,17 +1068,28 @@ export function createRSCHandler<
1014
1068
  nonce,
1015
1069
  );
1016
1070
  }
1017
- if (negotiated) {
1018
- response.headers.append("Vary", "Accept");
1071
+ if (negotiated && !isWebSocketUpgradeResponse(response)) {
1072
+ // handleRscRendering bakes `accept` into the RSC response's Vary list;
1073
+ // dedup so the negotiated append does not list accept twice.
1074
+ appendVaryAccept(response);
1019
1075
  }
1020
1076
  return response;
1021
1077
  } catch (error) {
1022
1078
  // Check if middleware/handler returned Response
1023
1079
  if (error instanceof Response) {
1080
+ // An action revalidation render is delivered to the client over the
1081
+ // same Flight-parsing path as a partial navigation, so a Response
1082
+ // thrown during it must be converted exactly like a partial one
1083
+ // (raw 200 -> hard-nav hint, 3xx -> Flight redirect). Without this,
1084
+ // the no-middleware path returns the raw Response (the with-middleware
1085
+ // path is already covered by the isPartial || actionContinuation
1086
+ // guard below).
1087
+ const treatAsPartial = isPartial || actionContinuation != null;
1088
+
1024
1089
  // During partial (client-side navigation), a 200 Response from a handler
1025
1090
  // means the route serves raw content (JSON, text, etc.), not JSX.
1026
1091
  // Signal the browser to hard-navigate so it renders the raw response.
1027
- if (isPartial && error.status === 200) {
1092
+ if (treatAsPartial && error.status === 200) {
1028
1093
  console.warn(
1029
1094
  `[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
1030
1095
  `Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`,
@@ -1038,7 +1103,7 @@ export function createRSCHandler<
1038
1103
  });
1039
1104
  }
1040
1105
 
1041
- if (isPartial) {
1106
+ if (treatAsPartial) {
1042
1107
  const intercepted = interceptRedirectForPartial(
1043
1108
  error,
1044
1109
  createRedirectFlightResponse,
@@ -1050,10 +1115,7 @@ export function createRSCHandler<
1050
1115
  }
1051
1116
 
1052
1117
  // Render 404 page for unmatched routes
1053
- const isRouteNotFound =
1054
- error instanceof RouteNotFoundError ||
1055
- (error instanceof Error && error.name === "RouteNotFoundError");
1056
- if (isRouteNotFound) {
1118
+ if (isRouteNotFoundError(error)) {
1057
1119
  callOnError(error, "routing", {
1058
1120
  request,
1059
1121
  url,
@@ -1084,13 +1146,26 @@ export function createRSCHandler<
1084
1146
  segments: [notFoundSegment],
1085
1147
  matched: [],
1086
1148
  diff: [],
1149
+ // Shape parity with buildFullPayload (rsc-rendering.ts): the 404
1150
+ // payload carries params/resolvedIds/prefetchCacheTTL the same way a
1151
+ // matched full render does. resolvedIds mirrors the rendered segment
1152
+ // list (the single notFound segment) like the error-boundary path
1153
+ // (match-api.ts) does for its boundary segment.
1154
+ resolvedIds: [notFoundSegment.id],
1155
+ params: {},
1087
1156
  isPartial: false,
1088
1157
  rootLayout: router.rootLayout,
1089
- handles: handleStore.stream(),
1158
+ // Full (404) render: resolve deferred handle values server-side.
1159
+ handles: resolvedHandleStream(handleStore),
1090
1160
  version,
1161
+ prefetchCacheTTL: router.prefetchCacheTTL,
1162
+ prefetchCacheSize: router.prefetchCacheSize,
1163
+ prefetchConcurrency: router.prefetchConcurrency,
1164
+ stateCookieName: router.resolvedStateCookieName,
1091
1165
  themeConfig: router.themeConfig,
1092
1166
  warmupEnabled: router.warmupEnabled,
1093
- initialTheme: requireRequestContext().theme,
1167
+ strictMode: router.strictMode,
1168
+ initialTheme: getRequestContext().theme,
1094
1169
  },
1095
1170
  };
1096
1171
 
@@ -1100,16 +1175,15 @@ export function createRSCHandler<
1100
1175
  },
1101
1176
  });
1102
1177
 
1103
- const isRscRequest =
1104
- isPartial ||
1105
- (!request.headers.get("accept")?.includes("text/html") &&
1106
- !url.searchParams.has("__html")) ||
1107
- url.searchParams.has("__rsc");
1108
-
1109
- if (isRscRequest) {
1178
+ if (isRscRequest(request, url, isPartial)) {
1110
1179
  return createResponseWithMergedHeaders(rscStream, {
1111
1180
  status: 404,
1112
- headers: { "content-type": "text/x-component;charset=utf-8" },
1181
+ headers: {
1182
+ "content-type": "text/x-component;charset=utf-8",
1183
+ // Router identity for the client's pre-decode integrity check; a
1184
+ // same-app 404 matches and applies in place. See response-adapter.
1185
+ "X-RSC-Router-Id": router.id,
1186
+ },
1113
1187
  });
1114
1188
  }
1115
1189
 
@@ -1118,7 +1192,7 @@ export function createRSCHandler<
1118
1192
  request,
1119
1193
  env,
1120
1194
  url,
1121
- requireRequestContext()._metricsStore,
1195
+ getRequestContext()._metricsStore,
1122
1196
  );
1123
1197
  const htmlStream = await ssrModule.renderHTML(rscStream, {
1124
1198
  nonce,