@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -13,7 +13,6 @@ 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,7 +30,12 @@ import {
31
30
  interceptRedirectForPartial,
32
31
  buildRouteMiddlewareEntries,
33
32
  } from "./helpers.js";
34
- import { isWebSocketUpgradeResponse } from "../response-utils.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";
35
39
  import {
36
40
  handleResponseRoute,
37
41
  type ResponseRouteMatch,
@@ -45,8 +49,6 @@ import {
45
49
  createReverseFunction,
46
50
  stripInternalParams,
47
51
  } from "../router/handler-context.js";
48
- import { getRouterContext } from "../router/router-context.js";
49
- import { resolveSink, safeEmit } from "../router/telemetry.js";
50
52
  import { contextSet } from "../context-var.js";
51
53
  import {
52
54
  hasCachedManifest,
@@ -57,6 +59,7 @@ import {
57
59
  getRouterTrie,
58
60
  } from "../route-map-builder.js";
59
61
  import type { HandlerContext } from "./handler-context.js";
62
+ import type { CacheErrorCategory } from "../cache/cache-error.js";
60
63
  import type { SegmentCacheStore } from "../cache/types.js";
61
64
  import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
62
65
  import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
@@ -82,6 +85,8 @@ import {
82
85
  appendMetric,
83
86
  buildMetricsTiming,
84
87
  } from "../router/metrics.js";
88
+ import { observePhase, PHASES } from "../router/instrument.js";
89
+ import { safeEmit, resolveSink, getRequestId } from "../router/telemetry.js";
85
90
  import {
86
91
  startSSRSetup,
87
92
  getSSRSetup,
@@ -128,12 +133,35 @@ import {
128
133
  * });
129
134
  * ```
130
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
+
131
152
  export function createRSCHandler<
132
153
  TEnv = unknown,
133
154
  TRoutes extends Record<string, string> = Record<string, string>,
134
155
  >(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
135
156
  const { router, version = VERSION, nonce: nonceProvider } = options;
136
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
+
137
165
  // Use provided deps or default to @vitejs/plugin-rsc/rsc exports
138
166
  const deps = options.deps ?? rscDeps;
139
167
  const {
@@ -219,23 +247,18 @@ export function createRSCHandler<
219
247
  metadata: { timeout: true, phase, durationMs },
220
248
  });
221
249
 
222
- try {
223
- const routerCtx = getRouterContext();
224
- if (routerCtx?.telemetry) {
225
- safeEmit(resolveSink(routerCtx.telemetry), {
226
- type: "request.timeout" as const,
227
- timestamp: performance.now(),
228
- requestId: routerCtx.requestId,
229
- phase,
230
- pathname: url.pathname,
231
- routeKey,
232
- actionId,
233
- durationMs,
234
- customHandler: !!router.onTimeout,
235
- });
236
- }
237
- } catch {
238
- // 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
+ });
239
262
  }
240
263
 
241
264
  if (router.onTimeout) {
@@ -268,16 +291,27 @@ export function createRSCHandler<
268
291
  function createRedirectFlightResponse(
269
292
  redirectUrl: string,
270
293
  locationState?: Record<string, unknown>,
294
+ external?: boolean,
271
295
  ): Response {
272
296
  const redirectPayload: RscPayload = {
273
297
  metadata: {
274
298
  pathname: redirectUrl,
275
299
  segments: [],
276
- redirect: { url: redirectUrl },
300
+ redirect: { url: redirectUrl, ...(external && { external: true }) },
277
301
  ...(locationState && { locationState }),
278
302
  },
279
303
  };
280
- 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
+ });
281
315
  return createResponseWithMergedHeaders(rscStream, {
282
316
  status: 200,
283
317
  headers: { "content-type": "text/x-component;charset=utf-8" },
@@ -425,10 +459,18 @@ export function createRSCHandler<
425
459
  url,
426
460
  variables,
427
461
  cacheStore,
462
+ explicitTaggedStores,
428
463
  cacheProfiles: router.cacheProfiles,
429
464
  executionContext: executionCtx,
430
465
  themeConfig: router.themeConfig,
466
+ stateCookieName: router.resolvedStateCookieName,
467
+ version,
431
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;
432
474
  if (earlyMetricsStore) {
433
475
  requestContext._debugPerformance = true;
434
476
  requestContext._metricsStore = earlyMetricsStore;
@@ -437,7 +479,7 @@ export function createRSCHandler<
437
479
  // can surface non-fatal errors through the router's onError callback.
438
480
  requestContext._reportBackgroundError = (
439
481
  error: unknown,
440
- category: string,
482
+ category: CacheErrorCategory,
441
483
  ) => {
442
484
  callOnError(error, "cache", {
443
485
  request,
@@ -470,81 +512,112 @@ export function createRSCHandler<
470
512
  // Store basename on request context (scoped per-request via existing ALS)
471
513
  requestContext._basename = router.basename;
472
514
 
473
- return runWithRequestContext(requestContext, async () => {
474
- // Core handler logic (wrapped by middleware)
475
- const coreHandler = async (): Promise<Response> => {
476
- return coreRequestHandler(request, env, url, variables, nonce);
477
- };
478
-
479
- // Execute middleware chain if any, otherwise call core handler directly
480
- let response: Response;
481
- if (matchedMiddleware.length > 0) {
482
- const mwResponse = await executeMiddleware(
483
- matchedMiddleware,
484
- request,
485
- env,
486
- variables,
487
- coreHandler,
488
- createReverseFunction(getRequiredRouteMap()),
489
- );
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
+ };
490
537
 
491
- if (
492
- url.searchParams.has("_rsc_partial") ||
493
- url.searchParams.has("_rsc_action")
494
- ) {
495
- const intercepted = interceptRedirectForPartial(
496
- mwResponse,
497
- 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()),
498
548
  );
499
- 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
+ }
500
562
  } else {
501
- response = finalizeResponse(mwResponse);
563
+ response = await coreHandler();
502
564
  }
503
- } else {
504
- response = await coreHandler();
505
- }
506
565
 
507
- // Finalize metrics after all middleware (including post-next work)
508
- // has completed so :post spans are captured in the timeline.
509
- // Handler timing parts are always emitted (even without debug metrics)
510
- // so non-debug requests still get bootstrap Server-Timing entries.
511
- const handlerTimingArr: string[] = variables.__handlerTiming || [];
512
- // Preserve any existing Server-Timing set by response routes or middleware
513
- const existingTiming = response.headers.get("Server-Timing");
514
- const timingParts = existingTiming
515
- ? [existingTiming, ...handlerTimingArr]
516
- : [...handlerTimingArr];
517
-
518
- const metricsStore = requestContext._metricsStore;
519
- if (metricsStore) {
520
- // When the store was created at handler start (earlyMetricsStore),
521
- // handler:total covers the full request. When ctx.debugPerformance()
522
- // created the store mid-request, use its requestStart to avoid a
523
- // negative startTime offset.
524
- const totalStart = earlyMetricsStore
525
- ? handlerStart
526
- : metricsStore.requestStart;
527
- appendMetric(
528
- metricsStore,
529
- "handler:total",
530
- totalStart,
531
- performance.now() - totalStart,
532
- );
533
- const metricsTiming = buildMetricsTiming(
534
- request.method,
535
- url.pathname,
536
- metricsStore,
537
- );
538
- if (metricsTiming) timingParts.push(metricsTiming);
539
- }
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
+ }
540
601
 
541
- const fullTiming = timingParts.join(", ");
542
- if (fullTiming && !isWebSocketUpgradeResponse(response)) {
543
- response.headers.set("Server-Timing", fullTiming);
544
- }
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
+ }
545
612
 
546
- return response;
547
- });
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
+ );
548
621
  };
549
622
 
550
623
  // Core request handling logic (separated for middleware wrapping).
@@ -642,13 +715,14 @@ export function createRSCHandler<
642
715
  console.log(
643
716
  `[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
644
717
  );
645
- return createResponseWithMergedHeaders(null, {
646
- status: 200,
647
- headers: {
648
- "X-RSC-Reload": plan.reloadUrl,
649
- "content-type": "text/x-component;charset=utf-8",
650
- },
651
- });
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);
652
726
  }
653
727
 
654
728
  // ---- 3. Origin guard (gate for action/loader/PE modes) ----
@@ -680,22 +754,17 @@ export function createRSCHandler<
680
754
  },
681
755
  });
682
756
 
683
- try {
684
- const routerCtx = getRouterContext();
685
- if (routerCtx?.telemetry) {
686
- safeEmit(resolveSink(routerCtx.telemetry), {
687
- type: "request.origin-rejected" as const,
688
- timestamp: performance.now(),
689
- requestId: routerCtx.requestId,
690
- method: request.method,
691
- pathname: url.pathname,
692
- phase: originPhase,
693
- origin: request.headers.get("origin"),
694
- host: request.headers.get("host"),
695
- });
696
- }
697
- } catch {
698
- // 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
+ });
699
768
  }
700
769
 
701
770
  return originResult;
@@ -726,11 +795,11 @@ export function createRSCHandler<
726
795
  nonce: string | undefined,
727
796
  ): Promise<Response> {
728
797
  // Common setup
729
- const handleStore = requireRequestContext()._handleStore;
798
+ const handleStore = getRequestContext()._handleStore;
730
799
 
731
800
  // Wire up error reporting for late streaming-handle failures
732
801
  handleStore.onError = (error: Error) => {
733
- const reqCtx = requireRequestContext();
802
+ const reqCtx = getRequestContext();
734
803
  callOnError(error, "handler", {
735
804
  request,
736
805
  url,
@@ -738,22 +807,17 @@ export function createRSCHandler<
738
807
  params: reqCtx.params as Record<string, string>,
739
808
  handledByBoundary: true,
740
809
  });
741
- try {
742
- const routerCtx = getRouterContext();
743
- if (routerCtx?.telemetry) {
744
- safeEmit(resolveSink(routerCtx.telemetry), {
745
- type: "handler.error" as const,
746
- timestamp: performance.now(),
747
- requestId: routerCtx.requestId,
748
- error,
749
- handledByBoundary: true,
750
- pathname: url.pathname,
751
- routeKey: reqCtx._routeName,
752
- params: reqCtx.params as Record<string, string>,
753
- });
754
- }
755
- } catch {
756
- // 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
+ });
757
821
  }
758
822
  };
759
823
 
@@ -762,7 +826,7 @@ export function createRSCHandler<
762
826
  // instead of calling resolveRoute again.
763
827
  if (plan.mode !== "redirect") {
764
828
  setRequestContextParams(plan.route.params, plan.route.routeKey);
765
- requireRequestContext()._classifiedRoute = plan.route;
829
+ getRequestContext()._classifiedRoute = plan.route;
766
830
  }
767
831
 
768
832
  const routeReverse = createReverseFunction(getRequiredRouteMap());
@@ -803,7 +867,9 @@ export function createRSCHandler<
803
867
  }
804
868
  const response = responseOutcome.result;
805
869
  if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
806
- response.headers.append("Vary", "Accept");
870
+ // handleResponseRoute (callHandlerWithVary) already appends Vary: Accept
871
+ // for negotiated responses; dedup so we don't emit Vary: Accept, Accept.
872
+ appendVaryAccept(response);
807
873
  }
808
874
  return response;
809
875
  }
@@ -811,14 +877,28 @@ export function createRSCHandler<
811
877
  // SSR setup: kick off in parallel for modes that need HTML rendering.
812
878
  // Placed after response-route short-circuit so response/mime routes
813
879
  // never pay for SSR work.
814
- 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) {
815
895
  variables[SSR_SETUP_VAR] = startSSRSetup(
816
896
  handlerCtx,
817
897
  request,
818
898
  env,
819
899
  url,
820
900
  router.debugPerformance
821
- ? () => requireRequestContext()._metricsStore
901
+ ? () => getRequestContext()._metricsStore
822
902
  : undefined,
823
903
  );
824
904
  }
@@ -859,14 +939,20 @@ export function createRSCHandler<
859
939
  if (plan.mode === "action") {
860
940
  let actionContinuation: ActionContinuation | undefined;
861
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.
862
946
  const actionOutcome = await withTimeout(
863
- executeServerAction(
864
- handlerCtx,
865
- request,
866
- env,
867
- url,
868
- plan.actionId,
869
- handleStore,
947
+ observePhase(PHASES.action(plan.actionId), () =>
948
+ executeServerAction(
949
+ handlerCtx,
950
+ request,
951
+ env,
952
+ url,
953
+ plan.actionId,
954
+ handleStore,
955
+ ),
870
956
  ),
871
957
  router.timeouts.actionMs,
872
958
  "action",
@@ -955,7 +1041,7 @@ export function createRSCHandler<
955
1041
  url: URL,
956
1042
  variables: Record<string, any>,
957
1043
  nonce: string | undefined,
958
- handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
1044
+ handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
959
1045
  isPartial: boolean,
960
1046
  actionContinuation?: ActionContinuation,
961
1047
  ): Promise<Response> {
@@ -983,16 +1069,27 @@ export function createRSCHandler<
983
1069
  );
984
1070
  }
985
1071
  if (negotiated && !isWebSocketUpgradeResponse(response)) {
986
- response.headers.append("Vary", "Accept");
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);
987
1075
  }
988
1076
  return response;
989
1077
  } catch (error) {
990
1078
  // Check if middleware/handler returned Response
991
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
+
992
1089
  // During partial (client-side navigation), a 200 Response from a handler
993
1090
  // means the route serves raw content (JSON, text, etc.), not JSX.
994
1091
  // Signal the browser to hard-navigate so it renders the raw response.
995
- if (isPartial && error.status === 200) {
1092
+ if (treatAsPartial && error.status === 200) {
996
1093
  console.warn(
997
1094
  `[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
998
1095
  `Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`,
@@ -1006,7 +1103,7 @@ export function createRSCHandler<
1006
1103
  });
1007
1104
  }
1008
1105
 
1009
- if (isPartial) {
1106
+ if (treatAsPartial) {
1010
1107
  const intercepted = interceptRedirectForPartial(
1011
1108
  error,
1012
1109
  createRedirectFlightResponse,
@@ -1049,13 +1146,26 @@ export function createRSCHandler<
1049
1146
  segments: [notFoundSegment],
1050
1147
  matched: [],
1051
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: {},
1052
1156
  isPartial: false,
1053
1157
  rootLayout: router.rootLayout,
1054
- handles: handleStore.stream(),
1158
+ // Full (404) render: resolve deferred handle values server-side.
1159
+ handles: resolvedHandleStream(handleStore),
1055
1160
  version,
1161
+ prefetchCacheTTL: router.prefetchCacheTTL,
1162
+ prefetchCacheSize: router.prefetchCacheSize,
1163
+ prefetchConcurrency: router.prefetchConcurrency,
1164
+ stateCookieName: router.resolvedStateCookieName,
1056
1165
  themeConfig: router.themeConfig,
1057
1166
  warmupEnabled: router.warmupEnabled,
1058
- initialTheme: requireRequestContext().theme,
1167
+ strictMode: router.strictMode,
1168
+ initialTheme: getRequestContext().theme,
1059
1169
  },
1060
1170
  };
1061
1171
 
@@ -1068,7 +1178,12 @@ export function createRSCHandler<
1068
1178
  if (isRscRequest(request, url, isPartial)) {
1069
1179
  return createResponseWithMergedHeaders(rscStream, {
1070
1180
  status: 404,
1071
- 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
+ },
1072
1187
  });
1073
1188
  }
1074
1189
 
@@ -1077,7 +1192,7 @@ export function createRSCHandler<
1077
1192
  request,
1078
1193
  env,
1079
1194
  url,
1080
- requireRequestContext()._metricsStore,
1195
+ getRequestContext()._metricsStore,
1081
1196
  );
1082
1197
  const htmlStream = await ssrModule.renderHTML(rscStream, {
1083
1198
  nonce,