@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad

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 (406) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +291 -61
  3. package/dist/bin/rango.js +544 -143
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3744 -1329
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +67 -13
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +312 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +247 -23
  12. package/skills/caching/SKILL.md +322 -19
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/debug-manifest/SKILL.md +4 -2
  16. package/skills/document-cache/SKILL.md +78 -55
  17. package/skills/handler-use/SKILL.md +364 -0
  18. package/skills/hooks/SKILL.md +282 -60
  19. package/skills/host-router/SKILL.md +278 -0
  20. package/skills/i18n/SKILL.md +276 -0
  21. package/skills/intercept/SKILL.md +50 -6
  22. package/skills/layout/SKILL.md +35 -9
  23. package/skills/links/SKILL.md +249 -17
  24. package/skills/loader/SKILL.md +297 -31
  25. package/skills/middleware/SKILL.md +52 -13
  26. package/skills/migrate-nextjs/SKILL.md +584 -0
  27. package/skills/migrate-react-router/SKILL.md +771 -0
  28. package/skills/mime-routes/SKILL.md +28 -1
  29. package/skills/observability/SKILL.md +172 -0
  30. package/skills/parallel/SKILL.md +203 -7
  31. package/skills/prerender/SKILL.md +155 -111
  32. package/skills/rango/SKILL.md +251 -23
  33. package/skills/react-compiler/SKILL.md +168 -0
  34. package/skills/response-routes/SKILL.md +123 -48
  35. package/skills/route/SKILL.md +104 -9
  36. package/skills/router-setup/SKILL.md +124 -11
  37. package/skills/scripts/SKILL.md +179 -0
  38. package/skills/server-actions/SKILL.md +775 -0
  39. package/skills/streams-and-websockets/SKILL.md +283 -0
  40. package/skills/tailwind/SKILL.md +27 -3
  41. package/skills/testing/SKILL.md +125 -222
  42. package/skills/testing/bindings.md +103 -0
  43. package/skills/testing/cache-prerender.md +127 -0
  44. package/skills/testing/client-components.md +124 -0
  45. package/skills/testing/e2e-parity.md +125 -0
  46. package/skills/testing/flight.md +91 -0
  47. package/skills/testing/handles.md +129 -0
  48. package/skills/testing/loader.md +128 -0
  49. package/skills/testing/middleware.md +99 -0
  50. package/skills/testing/render-handler.md +121 -0
  51. package/skills/testing/response-routes.md +95 -0
  52. package/skills/testing/reverse-and-types.md +84 -0
  53. package/skills/testing/server-actions.md +107 -0
  54. package/skills/testing/server-tree.md +128 -0
  55. package/skills/testing/setup.md +123 -0
  56. package/skills/typesafety/SKILL.md +357 -52
  57. package/skills/use-cache/SKILL.md +46 -14
  58. package/skills/view-transitions/SKILL.md +294 -0
  59. package/src/__augment-tests__/augment.ts +81 -0
  60. package/src/__augment-tests__/augmented.check.ts +116 -0
  61. package/src/__internal.ts +67 -40
  62. package/src/bin/rango.ts +18 -0
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +197 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/link-interceptor.ts +4 -0
  74. package/src/browser/navigation-bridge.ts +200 -30
  75. package/src/browser/navigation-client.ts +217 -58
  76. package/src/browser/navigation-store-handle.ts +38 -0
  77. package/src/browser/navigation-store.ts +76 -67
  78. package/src/browser/navigation-transaction.ts +18 -66
  79. package/src/browser/network-error-handler.ts +34 -7
  80. package/src/browser/partial-update.ts +187 -112
  81. package/src/browser/prefetch/cache.ts +312 -30
  82. package/src/browser/prefetch/fetch.ts +344 -47
  83. package/src/browser/prefetch/policy.ts +6 -0
  84. package/src/browser/prefetch/queue.ts +126 -20
  85. package/src/browser/prefetch/resource-ready.ts +77 -0
  86. package/src/browser/rango-state.ts +158 -76
  87. package/src/browser/react/Link.tsx +125 -18
  88. package/src/browser/react/NavigationProvider.tsx +135 -120
  89. package/src/browser/react/ScrollRestoration.tsx +10 -6
  90. package/src/browser/react/context.ts +7 -2
  91. package/src/browser/react/filter-segment-order.ts +66 -7
  92. package/src/browser/react/index.ts +0 -48
  93. package/src/browser/react/location-state-shared.ts +178 -8
  94. package/src/browser/react/location-state.ts +39 -14
  95. package/src/browser/react/use-action.ts +6 -15
  96. package/src/browser/react/use-handle.ts +23 -69
  97. package/src/browser/react/use-href.tsx +8 -1
  98. package/src/browser/react/use-link-status.ts +33 -8
  99. package/src/browser/react/use-navigation.ts +32 -7
  100. package/src/browser/react/use-params.ts +20 -10
  101. package/src/browser/react/use-reverse.ts +106 -0
  102. package/src/browser/react/use-router.ts +46 -11
  103. package/src/browser/react/use-search-params.ts +0 -5
  104. package/src/browser/react/use-segments.ts +11 -21
  105. package/src/browser/response-adapter.ts +80 -5
  106. package/src/browser/rsc-router.tsx +226 -75
  107. package/src/browser/scroll-restoration.ts +54 -42
  108. package/src/browser/segment-reconciler.ts +36 -9
  109. package/src/browser/segment-structure-assert.ts +2 -2
  110. package/src/browser/server-action-bridge.ts +619 -442
  111. package/src/browser/types.ts +115 -11
  112. package/src/browser/validate-redirect-origin.ts +43 -16
  113. package/src/build/collect-fallback-refs.ts +107 -0
  114. package/src/build/generate-manifest.ts +65 -40
  115. package/src/build/generate-route-types.ts +7 -1
  116. package/src/build/index.ts +8 -2
  117. package/src/build/prefix-tree-utils.ts +123 -0
  118. package/src/build/route-trie.ts +182 -37
  119. package/src/build/route-types/ast-route-extraction.ts +15 -8
  120. package/src/build/route-types/codegen.ts +16 -5
  121. package/src/build/route-types/include-resolution.ts +125 -24
  122. package/src/build/route-types/param-extraction.ts +6 -3
  123. package/src/build/route-types/per-module-writer.ts +22 -6
  124. package/src/build/route-types/router-processing.ts +392 -106
  125. package/src/build/route-types/scan-filter.ts +9 -2
  126. package/src/build/route-types/source-scan.ts +216 -0
  127. package/src/build/runtime-discovery.ts +9 -20
  128. package/src/cache/cache-error.ts +104 -0
  129. package/src/cache/cache-key-utils.ts +29 -13
  130. package/src/cache/cache-policy.ts +108 -34
  131. package/src/cache/cache-runtime.ts +214 -48
  132. package/src/cache/cache-scope.ts +236 -89
  133. package/src/cache/cache-tag.ts +103 -0
  134. package/src/cache/cf/cf-base64.ts +33 -0
  135. package/src/cache/cf/cf-cache-constants.ts +127 -0
  136. package/src/cache/cf/cf-cache-store.ts +2224 -171
  137. package/src/cache/cf/cf-cache-types.ts +349 -0
  138. package/src/cache/cf/cf-kv-utils.ts +46 -0
  139. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  140. package/src/cache/cf/index.ts +11 -17
  141. package/src/cache/document-cache.ts +89 -27
  142. package/src/cache/handle-snapshot.ts +70 -0
  143. package/src/cache/index.ts +11 -20
  144. package/src/cache/memory-segment-store.ts +136 -37
  145. package/src/cache/profile-registry.ts +31 -31
  146. package/src/cache/read-through-swr.ts +41 -11
  147. package/src/cache/segment-codec.ts +9 -17
  148. package/src/cache/tag-invalidation.ts +230 -0
  149. package/src/cache/taint.ts +55 -0
  150. package/src/cache/types.ts +37 -100
  151. package/src/client.rsc.tsx +45 -21
  152. package/src/client.tsx +120 -336
  153. package/src/cloudflare/index.ts +11 -0
  154. package/src/cloudflare/tracing.ts +109 -0
  155. package/src/component-utils.ts +19 -0
  156. package/src/components/DefaultDocument.tsx +8 -2
  157. package/src/context-var.ts +84 -2
  158. package/src/debug.ts +2 -2
  159. package/src/decode-loader-results.ts +52 -0
  160. package/src/defer.ts +196 -0
  161. package/src/deps/ssr.ts +0 -1
  162. package/src/encode-kv.ts +49 -0
  163. package/src/errors.ts +30 -4
  164. package/src/escape-script.ts +52 -0
  165. package/src/handle.ts +70 -22
  166. package/src/handles/MetaTags.tsx +56 -19
  167. package/src/handles/Scripts.tsx +183 -0
  168. package/src/handles/breadcrumbs.ts +95 -0
  169. package/src/handles/is-thenable.ts +19 -0
  170. package/src/handles/meta.ts +51 -40
  171. package/src/handles/script.ts +244 -0
  172. package/src/host/cookie-handler.ts +9 -60
  173. package/src/host/errors.ts +0 -24
  174. package/src/host/index.ts +8 -5
  175. package/src/host/pattern-matcher.ts +23 -52
  176. package/src/host/router.ts +107 -99
  177. package/src/host/testing.ts +40 -27
  178. package/src/host/types.ts +37 -4
  179. package/src/host/utils.ts +1 -1
  180. package/src/href-client.ts +137 -22
  181. package/src/index.rsc.ts +79 -29
  182. package/src/index.ts +149 -65
  183. package/src/internal-debug.ts +11 -10
  184. package/src/loader-store.ts +500 -0
  185. package/src/loader.rsc.ts +20 -13
  186. package/src/loader.ts +12 -11
  187. package/src/missing-id-error.ts +68 -0
  188. package/src/outlet-context.ts +1 -1
  189. package/src/outlet-provider.tsx +1 -5
  190. package/src/prerender/param-hash.ts +16 -16
  191. package/src/prerender/store.ts +63 -26
  192. package/src/prerender.ts +198 -82
  193. package/src/redirect-origin.ts +100 -0
  194. package/src/regex-escape.ts +8 -0
  195. package/src/render-error-thrower.tsx +20 -0
  196. package/src/response-utils.ts +62 -0
  197. package/src/reverse.ts +65 -15
  198. package/src/root-error-boundary.tsx +1 -19
  199. package/src/route-content-wrapper.tsx +7 -72
  200. package/src/route-definition/dsl-helpers.ts +469 -276
  201. package/src/route-definition/helper-factories.ts +29 -139
  202. package/src/route-definition/helpers-types.ts +113 -37
  203. package/src/route-definition/index.ts +3 -3
  204. package/src/route-definition/redirect.ts +53 -12
  205. package/src/route-definition/resolve-handler-use.ts +161 -0
  206. package/src/route-definition/use-item-types.ts +32 -0
  207. package/src/route-map-builder.ts +7 -17
  208. package/src/route-types.ts +37 -41
  209. package/src/router/basename.ts +14 -0
  210. package/src/router/content-negotiation.ts +164 -17
  211. package/src/router/error-handling.ts +45 -18
  212. package/src/router/find-match.ts +45 -22
  213. package/src/router/handler-context.ts +110 -39
  214. package/src/router/instrument.ts +350 -0
  215. package/src/router/intercept-resolution.ts +50 -24
  216. package/src/router/lazy-includes.ts +19 -53
  217. package/src/router/loader-resolution.ts +274 -56
  218. package/src/router/logging.ts +5 -8
  219. package/src/router/manifest.ts +49 -45
  220. package/src/router/match-api.ts +121 -205
  221. package/src/router/match-context.ts +0 -22
  222. package/src/router/match-handlers.ts +58 -58
  223. package/src/router/match-middleware/background-revalidation.ts +33 -6
  224. package/src/router/match-middleware/cache-lookup.ts +214 -263
  225. package/src/router/match-middleware/cache-store.ts +73 -33
  226. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  227. package/src/router/match-middleware/segment-resolution.ts +52 -18
  228. package/src/router/match-pipelines.ts +1 -42
  229. package/src/router/match-result.ts +104 -49
  230. package/src/router/metrics.ts +217 -26
  231. package/src/router/middleware-types.ts +24 -110
  232. package/src/router/middleware.ts +384 -197
  233. package/src/router/navigation-snapshot.ts +131 -0
  234. package/src/router/params-util.ts +23 -0
  235. package/src/router/pattern-matching.ts +148 -91
  236. package/src/router/prefetch-cache-ttl.ts +51 -0
  237. package/src/router/prerender-match.ts +199 -56
  238. package/src/router/preview-match.ts +32 -102
  239. package/src/router/request-classification.ts +276 -0
  240. package/src/router/revalidation.ts +144 -74
  241. package/src/router/route-snapshot.ts +244 -0
  242. package/src/router/router-context.ts +8 -28
  243. package/src/router/router-interfaces.ts +129 -36
  244. package/src/router/router-options.ts +185 -23
  245. package/src/router/router-registry.ts +2 -5
  246. package/src/router/segment-resolution/fresh.ts +281 -76
  247. package/src/router/segment-resolution/helpers.ts +116 -31
  248. package/src/router/segment-resolution/loader-cache.ts +63 -37
  249. package/src/router/segment-resolution/revalidation.ts +493 -391
  250. package/src/router/segment-resolution/static-store.ts +19 -5
  251. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  252. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  253. package/src/router/segment-resolution.ts +5 -1
  254. package/src/router/segment-wrappers.ts +8 -5
  255. package/src/router/state-cookie-name.ts +33 -0
  256. package/src/router/substitute-pattern-params.ts +56 -0
  257. package/src/router/telemetry-otel.ts +161 -199
  258. package/src/router/telemetry.ts +96 -19
  259. package/src/router/timeout.ts +0 -20
  260. package/src/router/tracing.ts +206 -0
  261. package/src/router/trie-matching.ts +180 -58
  262. package/src/router/types.ts +10 -63
  263. package/src/router/url-params.ts +44 -0
  264. package/src/router.ts +182 -54
  265. package/src/rsc/handler-context.ts +3 -2
  266. package/src/rsc/handler.ts +702 -460
  267. package/src/rsc/helpers.ts +168 -46
  268. package/src/rsc/index.ts +2 -25
  269. package/src/rsc/json-route-result.ts +38 -0
  270. package/src/rsc/loader-fetch.ts +127 -31
  271. package/src/rsc/manifest-init.ts +33 -42
  272. package/src/rsc/origin-guard.ts +39 -25
  273. package/src/rsc/progressive-enhancement.ts +98 -19
  274. package/src/rsc/redirect-guard.ts +99 -0
  275. package/src/rsc/response-cache-serve.ts +238 -0
  276. package/src/rsc/response-error.ts +79 -12
  277. package/src/rsc/response-route-handler.ts +99 -189
  278. package/src/rsc/rsc-rendering.ts +126 -106
  279. package/src/rsc/runtime-warnings.ts +23 -10
  280. package/src/rsc/server-action.ts +269 -114
  281. package/src/rsc/ssr-setup.ts +144 -0
  282. package/src/rsc/types.ts +34 -6
  283. package/src/runtime-env.ts +18 -0
  284. package/src/search-params.ts +49 -41
  285. package/src/segment-content-promise.ts +67 -0
  286. package/src/segment-loader-promise.ts +149 -0
  287. package/src/segment-system.tsx +281 -129
  288. package/src/serialize.ts +243 -0
  289. package/src/server/context.ts +317 -63
  290. package/src/server/cookie-parse.ts +32 -0
  291. package/src/server/cookie-store.ts +80 -5
  292. package/src/server/handle-store.ts +40 -38
  293. package/src/server/loader-registry.ts +26 -46
  294. package/src/server/request-context.ts +425 -177
  295. package/src/server.ts +6 -0
  296. package/src/ssr/index.tsx +25 -16
  297. package/src/static-handler.ts +27 -18
  298. package/src/testing/cache-status.ts +162 -0
  299. package/src/testing/collect-handle.ts +40 -0
  300. package/src/testing/dispatch.ts +701 -0
  301. package/src/testing/dom.entry.ts +22 -0
  302. package/src/testing/e2e/fixture.ts +188 -0
  303. package/src/testing/e2e/index.ts +128 -0
  304. package/src/testing/e2e/matchers.ts +35 -0
  305. package/src/testing/e2e/page-helpers.ts +272 -0
  306. package/src/testing/e2e/parity.ts +387 -0
  307. package/src/testing/e2e/server.ts +195 -0
  308. package/src/testing/flight-matchers.ts +97 -0
  309. package/src/testing/flight-normalize.ts +11 -0
  310. package/src/testing/flight-runtime.d.ts +57 -0
  311. package/src/testing/flight-tree.ts +682 -0
  312. package/src/testing/flight.entry.ts +52 -0
  313. package/src/testing/flight.ts +257 -0
  314. package/src/testing/generated-routes.ts +183 -0
  315. package/src/testing/index.ts +99 -0
  316. package/src/testing/internal/context.ts +371 -0
  317. package/src/testing/internal/flight-client-globals.ts +30 -0
  318. package/src/testing/internal/seed-vars.ts +54 -0
  319. package/src/testing/render-handler.ts +343 -0
  320. package/src/testing/render-route.tsx +581 -0
  321. package/src/testing/run-loader.ts +385 -0
  322. package/src/testing/run-middleware.ts +205 -0
  323. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  324. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  325. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  326. package/src/testing/vitest-stubs/version.ts +5 -0
  327. package/src/testing/vitest.ts +305 -0
  328. package/src/theme/ThemeProvider.tsx +20 -58
  329. package/src/theme/ThemeScript.tsx +7 -9
  330. package/src/theme/constants.ts +52 -13
  331. package/src/theme/index.ts +3 -19
  332. package/src/theme/theme-context.ts +1 -5
  333. package/src/theme/theme-script.ts +22 -21
  334. package/src/theme/use-theme.ts +0 -3
  335. package/src/types/boundaries.ts +0 -35
  336. package/src/types/cache-types.ts +17 -8
  337. package/src/types/error-types.ts +30 -90
  338. package/src/types/global-namespace.ts +54 -41
  339. package/src/types/handler-context.ts +236 -88
  340. package/src/types/index.ts +1 -10
  341. package/src/types/loader-types.ts +44 -15
  342. package/src/types/request-scope.ts +112 -0
  343. package/src/types/route-config.ts +10 -45
  344. package/src/types/route-entry.ts +19 -7
  345. package/src/types/segments.ts +37 -19
  346. package/src/urls/include-helper.ts +33 -70
  347. package/src/urls/index.ts +1 -11
  348. package/src/urls/path-helper-types.ts +58 -11
  349. package/src/urls/path-helper.ts +57 -111
  350. package/src/urls/pattern-types.ts +48 -19
  351. package/src/urls/response-types.ts +25 -22
  352. package/src/urls/type-extraction.ts +58 -139
  353. package/src/urls/urls-function.ts +1 -18
  354. package/src/use-loader.tsx +346 -89
  355. package/src/vite/debug.ts +185 -0
  356. package/src/vite/discovery/bundle-postprocess.ts +64 -91
  357. package/src/vite/discovery/discover-routers.ts +147 -88
  358. package/src/vite/discovery/discovery-errors.ts +194 -0
  359. package/src/vite/discovery/gate-state.ts +171 -0
  360. package/src/vite/discovery/prerender-collection.ts +247 -145
  361. package/src/vite/discovery/route-types-writer.ts +40 -84
  362. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  363. package/src/vite/discovery/state.ts +61 -13
  364. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  365. package/src/vite/index.ts +10 -3
  366. package/src/vite/inject-client-debug.ts +36 -0
  367. package/src/vite/plugin-types.ts +155 -65
  368. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  369. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  370. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  371. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  372. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  373. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  374. package/src/vite/plugins/expose-action-id.ts +49 -98
  375. package/src/vite/plugins/expose-id-utils.ts +96 -51
  376. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  377. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  378. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  379. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  380. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  381. package/src/vite/plugins/performance-tracks.ts +89 -0
  382. package/src/vite/plugins/refresh-cmd.ts +127 -0
  383. package/src/vite/plugins/use-cache-transform.ts +73 -83
  384. package/src/vite/plugins/version-injector.ts +21 -25
  385. package/src/vite/plugins/version-plugin.ts +46 -37
  386. package/src/vite/plugins/virtual-entries.ts +13 -18
  387. package/src/vite/rango.ts +241 -287
  388. package/src/vite/router-discovery.ts +956 -149
  389. package/src/vite/utils/ast-handler-extract.ts +26 -35
  390. package/src/vite/utils/banner.ts +4 -4
  391. package/src/vite/utils/bundle-analysis.ts +10 -15
  392. package/src/vite/utils/client-chunks.ts +184 -0
  393. package/src/vite/utils/directive-prologue.ts +40 -0
  394. package/src/vite/utils/forward-user-plugins.ts +171 -0
  395. package/src/vite/utils/manifest-utils.ts +4 -59
  396. package/src/vite/utils/package-resolution.ts +20 -52
  397. package/src/vite/utils/prerender-utils.ts +141 -34
  398. package/src/vite/utils/shared-utils.ts +92 -42
  399. package/CLAUDE.md +0 -5
  400. package/src/browser/action-response-classifier.ts +0 -99
  401. package/src/browser/react/use-client-cache.ts +0 -58
  402. package/src/browser/shallow.ts +0 -40
  403. package/src/handles/index.ts +0 -6
  404. package/src/network-error-thrower.tsx +0 -23
  405. package/src/route-definition/route-function.ts +0 -119
  406. package/src/router/middleware-cookies.ts +0 -55
@@ -5,18 +5,19 @@ import {
5
5
  runWithRequestContext,
6
6
  type RequestContext,
7
7
  } from "../server/request-context.js";
8
- import { contextGet, contextSet } from "../context-var.js";
8
+ import { contextGet, contextSet, hasContextVars } from "../context-var.js";
9
9
  import {
10
10
  createPrerenderContext,
11
11
  createStaticContext,
12
12
  createReverseFunction,
13
13
  } from "./handler-context.js";
14
- import { isPrerenderPassthrough } from "../prerender.js";
14
+ import { detectPrerenderPassthrough } from "../prerender.js";
15
15
  import { isRouteRootScoped } from "../route-map-builder.js";
16
16
  import { setupBuildUse } from "./loader-resolution.js";
17
17
  import { loadManifest } from "./manifest.js";
18
18
  import { traverseBack } from "./pattern-matching.js";
19
19
  import type { RouterContext } from "./router-context.js";
20
+ import type { ResolveSegmentOptions } from "./segment-resolution.js";
20
21
  import { runWithRouterContext } from "./router-context.js";
21
22
  import type { EntryData, InterceptEntry } from "../server/context";
22
23
  import type {
@@ -40,7 +41,7 @@ export interface PrerenderMatchDeps<TEnv = any> {
40
41
  params: Record<string, string>,
41
42
  context: HandlerContext<any, TEnv>,
42
43
  loaderPromises: Map<string, Promise<any>>,
43
- options?: { skipLoaders?: boolean },
44
+ options?: ResolveSegmentOptions,
44
45
  ) => Promise<ResolvedSegment[]>;
45
46
  }
46
47
 
@@ -54,13 +55,21 @@ export async function matchForPrerender<TEnv = any>(
54
55
  deps: PrerenderMatchDeps<TEnv>,
55
56
  buildVars?: Record<string, any>,
56
57
  isPassthroughRoute?: boolean,
58
+ buildEnv?: TEnv,
59
+ /** Dev-only: check getParams() for passthrough routes to skip unknown params. */
60
+ devMode?: boolean,
57
61
  ): Promise<{
58
62
  segments: SerializedSegmentData[];
59
- handles: Record<string, SegmentHandleData>;
63
+ /** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. Encoded in
64
+ * the producer (where the Flight codec resolves) so the node-side build/dev
65
+ * sinks can persist it without touching the codec. */
66
+ handles: string;
60
67
  routeName: string;
61
68
  params: Record<string, string>;
62
69
  interceptSegments?: SerializedSegmentData[];
63
- interceptHandles?: Record<string, SegmentHandleData>;
70
+ /** RSC-encoded MERGED (main + intercept) handle map for the intercept artifact;
71
+ * the sinks store it as-is (no longer merge raw records). */
72
+ interceptHandles?: string;
64
73
  passthrough?: true;
65
74
  } | null> {
66
75
  // 1. Find the matching route entry
@@ -74,6 +83,17 @@ export async function matchForPrerender<TEnv = any>(
74
83
  // Build RouterContext for loadManifest/traverseBack
75
84
  const routerCtx = deps.buildRouterContext();
76
85
 
86
+ // Passthrough sentinel result: an unknown-param Passthrough route falls
87
+ // through to the live handler at runtime, so no artifact is baked. A fresh
88
+ // object is returned per call (no site mutates or identity-compares it).
89
+ const passthroughResult = () => ({
90
+ segments: [],
91
+ handles: "",
92
+ routeName: matched.routeKey,
93
+ params: matchedParams,
94
+ passthrough: true as const,
95
+ });
96
+
77
97
  return runWithRouterContext(routerCtx, async () => {
78
98
  // 2. Load the manifest entry tree
79
99
  const manifestEntry = await loadManifest(
@@ -90,20 +110,91 @@ export async function matchForPrerender<TEnv = any>(
90
110
  entries.push(entry);
91
111
  }
92
112
 
113
+ // 3b. Dev-mode passthrough shortcut: if the route is a Passthrough route
114
+ // and has getParams(), check if the matched params are in the known list.
115
+ // In production, only known params are pre-rendered; unknown params fall
116
+ // through to the live handler. Mirror that behavior in dev mode to avoid
117
+ // rendering unknown params with build: true.
118
+ // Vars collected from getParams() probe — merged into render context below.
119
+ let devProbeBuildVars: Record<string, any> | undefined;
120
+
121
+ if (devMode && matchedPassthroughRoute) {
122
+ const routeEntry = entries.find(
123
+ (
124
+ e,
125
+ ): e is EntryData & {
126
+ type: "route";
127
+ prerenderDef: { getParams: (ctx: any) => Promise<any[]> | any[] };
128
+ } =>
129
+ e.type === "route" &&
130
+ !!(e as any).isPassthrough &&
131
+ !!(e as any).prerenderDef?.getParams,
132
+ );
133
+ if (routeEntry) {
134
+ try {
135
+ const probeBuildVars: Record<string, any> = {};
136
+ const knownParamsList = await routeEntry.prerenderDef.getParams({
137
+ build: true as const,
138
+ dev: true,
139
+ set: ((keyOrVar: any, value: any) => {
140
+ contextSet(probeBuildVars, keyOrVar, value);
141
+ }) as any,
142
+ reverse: createReverseFunction(deps.mergedRouteMap),
143
+ get env() {
144
+ if (buildEnv !== undefined) return buildEnv;
145
+ throw new Error(
146
+ "[rango] ctx.env is not available during dev-mode getParams(). " +
147
+ "Configure buildEnv in your rango() plugin options to enable build-time env access.",
148
+ );
149
+ },
150
+ });
151
+ // Compare only the keys returned by getParams — ignore mount params
152
+ // from include() prefixes that aren't part of the handler's params.
153
+ const isKnown = knownParamsList.some((known: Record<string, any>) => {
154
+ const knownKeys = Object.keys(known);
155
+ return knownKeys.every(
156
+ (k) => String(known[k]) === String(matchedParams[k]),
157
+ );
158
+ });
159
+ if (!isKnown) {
160
+ return passthroughResult();
161
+ }
162
+ // Preserve vars set by getParams() for the render context
163
+ if (hasContextVars(probeBuildVars)) {
164
+ devProbeBuildVars = probeBuildVars;
165
+ }
166
+ } catch (err: any) {
167
+ // Mirror production semantics (prerender-collection.ts):
168
+ // Skip errors are intentional — treat as passthrough.
169
+ // All other errors propagate so dev surfaces them.
170
+ if (err?.name === "Skip") {
171
+ return passthroughResult();
172
+ }
173
+ throw err;
174
+ }
175
+ }
176
+ }
177
+
93
178
  // 4. Create handle store for collecting handle data
94
179
  const handleStore = createHandleStore();
95
180
 
96
181
  // 5. Create a minimal request context with the handle store
97
- // Shallow-copy getParams vars so each param set is independent
98
- const variables: Record<string, any> = buildVars ? { ...buildVars } : {};
182
+ // Shallow-copy getParams vars so each param set is independent.
183
+ // In dev mode, merge vars from the getParams() probe if the caller
184
+ // didn't provide buildVars (production passes them from expandPrerenderRoutes).
185
+ const effectiveBuildVars = buildVars ?? devProbeBuildVars;
186
+ const variables: Record<string, any> = effectiveBuildVars
187
+ ? { ...effectiveBuildVars }
188
+ : {};
99
189
  const stubRes = new Response(null, { status: 200 });
100
190
  const minimalRequestContext: RequestContext<TEnv> = {
101
- env: {} as TEnv,
191
+ env: buildEnv ?? ({} as TEnv),
102
192
  request: new Request("http://prerender" + pathname),
103
193
  url: new URL("http://prerender" + pathname),
194
+ originalUrl: new URL("http://prerender" + pathname),
104
195
  pathname,
105
196
  searchParams: new URLSearchParams(),
106
- var: variables,
197
+ _variables: variables,
107
198
  get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
108
199
  set: ((keyOrVar: any, value: any) => {
109
200
  contextSet(variables, keyOrVar, value);
@@ -116,16 +207,22 @@ export async function matchForPrerender<TEnv = any>(
116
207
  deleteCookie: () => {},
117
208
  header: () => {},
118
209
  setStatus: () => {},
210
+ _setStatus: () => {},
211
+ _rotateStateCookie: () => {},
212
+ _setKeepCacheDirective: () => {},
119
213
  use: (() => {
120
214
  throw new Error("use() not available during pre-rendering");
121
215
  }) as any,
122
216
  method: "GET",
123
217
  _handleStore: handleStore,
218
+ _requestTags: new Set<string>(),
124
219
  waitUntil: () => {},
125
220
  onResponse: () => {},
126
221
  _onResponseCallbacks: [],
127
222
  setLocationState() {},
128
223
  _locationState: undefined,
224
+ _renderBarrier: Promise.resolve(),
225
+ _resolveRenderBarrier: () => {},
129
226
  _reportedErrors: new WeakSet<object>(),
130
227
  reverse: createReverseFunction(
131
228
  deps.mergedRouteMap,
@@ -138,7 +235,7 @@ export async function matchForPrerender<TEnv = any>(
138
235
  return runWithRequestContext(minimalRequestContext, async () => {
139
236
  // 6. Create prerender context with synthetic URL.
140
237
  // Prerender handlers get params, pathname, url, searchParams, search,
141
- // reverse, and use(handle) — but no request, env, headers, or cookies.
238
+ // reverse, use(handle), and optionally env (when buildEnv is configured).
142
239
  const buildCtx = createPrerenderContext<TEnv>(
143
240
  matchedParams,
144
241
  pathname,
@@ -146,6 +243,8 @@ export async function matchForPrerender<TEnv = any>(
146
243
  matched.routeKey,
147
244
  variables,
148
245
  matchedPassthroughRoute,
246
+ buildEnv,
247
+ devMode,
149
248
  );
150
249
 
151
250
  // 7. Wire use() for handles only (loaders throw)
@@ -159,20 +258,18 @@ export async function matchForPrerender<TEnv = any>(
159
258
  matchedParams,
160
259
  buildCtx,
161
260
  loaderPromises,
162
- { skipLoaders: true },
261
+ // throwOnError: a render failure (or `throw new Skip()`) must reach the
262
+ // build/dev caller, not be baked into a frozen error page (issue #587).
263
+ { skipLoaders: true, throwOnError: true },
163
264
  );
164
265
 
165
- // 9. Detect passthrough sentinel: handler returned ctx.passthrough()
166
- for (const seg of allSegments) {
167
- if (isPrerenderPassthrough(seg.component)) {
168
- return {
169
- segments: [],
170
- handles: {},
171
- routeName: matched.routeKey,
172
- params: matchedParams,
173
- passthrough: true as const,
174
- };
175
- }
266
+ // 9. Detect passthrough sentinel: handler returned ctx.passthrough().
267
+ // When the route declares loading(), the handler result is deferred so the
268
+ // component is a thenable resolving to the sentinel — detectPrerenderPassthrough
269
+ // resolves thenables before testing (a sync check would miss it and bake a
270
+ // corrupt artifact).
271
+ if (await detectPrerenderPassthrough(allSegments)) {
272
+ return passthroughResult();
176
273
  }
177
274
 
178
275
  // 10. Filter out any loader segments (belt-and-suspenders)
@@ -184,16 +281,22 @@ export async function matchForPrerender<TEnv = any>(
184
281
 
185
282
  // 12. Serialize segments using the cache serializer
186
283
  const { serializeSegments } = await import("../cache/segment-codec.js");
284
+ const { encodeHandles } = await import("../cache/handle-snapshot.js");
187
285
  const serializedSegments = await serializeSegments(nonLoaderSegments);
188
286
 
189
- // 13. Collect handle data per segment (skip segments with no handle data)
190
- const handles: Record<string, SegmentHandleData> = {};
287
+ // 13. Collect handle data per segment (skip segments with no handle data).
288
+ // Encoded through the Flight codec (not stored raw) so Promise/ReactNode
289
+ // handle values survive the JSON-serialized build artifact / dev wire —
290
+ // the same fix the runtime cache uses. Encode happens here, in the RSC
291
+ // environment where the codec resolves; the node-side sinks only persist.
292
+ const handlesRecord: Record<string, SegmentHandleData> = {};
191
293
  for (const seg of nonLoaderSegments) {
192
294
  const segHandles = handleStore.getDataForSegment(seg.id);
193
295
  if (Object.keys(segHandles).length > 0) {
194
- handles[seg.id] = segHandles;
296
+ handlesRecord[seg.id] = segHandles;
195
297
  }
196
298
  }
299
+ const handles = await encodeHandles(handlesRecord);
197
300
 
198
301
  // Use the trie-level route key (e.g., "docs", "docs.article")
199
302
  const routeName = matched.routeKey;
@@ -203,7 +306,7 @@ export async function matchForPrerender<TEnv = any>(
203
306
  // evaluation -- we pre-render all intercepts unconditionally and let
204
307
  // runtime matching decide which to serve.
205
308
  let interceptSegments: SerializedSegmentData[] | undefined;
206
- let interceptHandles: Record<string, SegmentHandleData> | undefined;
309
+ let interceptHandles: string | undefined;
207
310
 
208
311
  const foundIntercepts: {
209
312
  intercept: InterceptEntry;
@@ -211,24 +314,14 @@ export async function matchForPrerender<TEnv = any>(
211
314
  }[] = [];
212
315
  let current: EntryData | null = manifestEntry;
213
316
  while (current) {
214
- if (current.intercept && current.intercept.length > 0) {
215
- for (const ic of current.intercept) {
317
+ // Flatten the entry and its sibling layouts into one source list, the
318
+ // same traversal findInterceptForRoute uses; the build keeps ALL matches
319
+ // (not just the innermost) and skips when(). intercept/layout are
320
+ // non-optional arrays, so empty ones are a no-op here.
321
+ for (const source of [current, ...current.layout]) {
322
+ for (const ic of source.intercept) {
216
323
  if (ic.routeName === matched.routeKey) {
217
- foundIntercepts.push({ intercept: ic, entry: current });
218
- }
219
- }
220
- }
221
- if (current.layout && current.layout.length > 0) {
222
- for (const siblingLayout of current.layout) {
223
- if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
224
- for (const ic of siblingLayout.intercept) {
225
- if (ic.routeName === matched.routeKey) {
226
- foundIntercepts.push({
227
- intercept: ic,
228
- entry: siblingLayout,
229
- });
230
- }
231
- }
324
+ foundIntercepts.push({ intercept: ic, entry: source });
232
325
  }
233
326
  }
234
327
  }
@@ -239,6 +332,18 @@ export async function matchForPrerender<TEnv = any>(
239
332
  const interceptResolvedSegments: typeof nonLoaderSegments = [];
240
333
 
241
334
  for (const { intercept, entry: parentEntry } of foundIntercepts) {
335
+ // setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
336
+ // resolveAllSegments pass left it on the route's segment id, so pin it
337
+ // to THIS intercept's slot id before resolving the handler/layout --
338
+ // otherwise the intercept's ctx.use() handle pushes land in the wrong
339
+ // bucket and getDataForSegment(seg.id) below drops them from the baked
340
+ // artifact. Re-pinned per iteration so multiple intercepts targeting
341
+ // the same route each get their own id (mirrors fresh.ts parallel-slot
342
+ // pinning).
343
+ const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
344
+ (buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
345
+ interceptSegmentId;
346
+
242
347
  // Resolve handler
243
348
  const handlerRaw =
244
349
  typeof intercept.handler === "function"
@@ -265,7 +370,7 @@ export async function matchForPrerender<TEnv = any>(
265
370
  }
266
371
 
267
372
  interceptResolvedSegments.push({
268
- id: `${parentEntry.shortCode}.${intercept.slotName}`,
373
+ id: interceptSegmentId,
269
374
  namespace: `intercept:${intercept.routeName}`,
270
375
  type: "parallel" as const,
271
376
  index: 0,
@@ -285,13 +390,20 @@ export async function matchForPrerender<TEnv = any>(
285
390
  interceptSegments = await serializeSegments(
286
391
  interceptResolvedSegments,
287
392
  );
288
- interceptHandles = {};
393
+ const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
289
394
  for (const seg of interceptResolvedSegments) {
290
395
  const segHandles = handleStore.getDataForSegment(seg.id);
291
396
  if (Object.keys(segHandles).length > 0) {
292
- interceptHandles[seg.id] = segHandles;
397
+ interceptHandlesRecord[seg.id] = segHandles;
293
398
  }
294
399
  }
400
+ // The intercept artifact serves main + intercept segments together, so
401
+ // encode the MERGED handle map here (the sinks no longer merge raw
402
+ // records — they store this pre-encoded string as-is).
403
+ interceptHandles = await encodeHandles({
404
+ ...handlesRecord,
405
+ ...interceptHandlesRecord,
406
+ });
295
407
  }
296
408
  }
297
409
 
@@ -318,7 +430,9 @@ export async function renderStaticSegment<TEnv = any>(
318
430
  handlerId: string,
319
431
  mergedRouteMap: Record<string, string>,
320
432
  routeName?: string,
321
- ): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null> {
433
+ buildEnv?: TEnv,
434
+ devMode?: boolean,
435
+ ): Promise<{ encoded: string; handles: string } | null> {
322
436
  const syntheticUrl = new URL("http://prerender/");
323
437
  const syntheticRequest = new Request(syntheticUrl);
324
438
 
@@ -328,12 +442,13 @@ export async function renderStaticSegment<TEnv = any>(
328
442
  // Minimal request context so setupBuildUse can find the HandleStore
329
443
  const stubRes = new Response(null, { status: 200 });
330
444
  const minimalRequestContext: RequestContext<TEnv> = {
331
- env: {} as TEnv,
445
+ env: buildEnv ?? ({} as TEnv),
332
446
  request: syntheticRequest,
333
447
  url: syntheticUrl,
448
+ originalUrl: syntheticUrl,
334
449
  pathname: "/",
335
450
  searchParams: syntheticUrl.searchParams,
336
- var: {},
451
+ _variables: {},
337
452
  get: () => undefined as any,
338
453
  set: () => {},
339
454
  params: {},
@@ -344,16 +459,22 @@ export async function renderStaticSegment<TEnv = any>(
344
459
  deleteCookie: () => {},
345
460
  header: () => {},
346
461
  setStatus: () => {},
462
+ _setStatus: () => {},
463
+ _rotateStateCookie: () => {},
464
+ _setKeepCacheDirective: () => {},
347
465
  use: (() => {
348
466
  throw new Error("use() not available during static pre-rendering");
349
467
  }) as any,
350
468
  method: "GET",
351
469
  _handleStore: handleStore,
470
+ _requestTags: new Set<string>(),
352
471
  waitUntil: () => {},
353
472
  onResponse: () => {},
354
473
  _onResponseCallbacks: [],
355
474
  setLocationState() {},
356
475
  _locationState: undefined,
476
+ _renderBarrier: Promise.resolve(),
477
+ _resolveRenderBarrier: () => {},
357
478
  _reportedErrors: new WeakSet<object>(),
358
479
  reverse: createReverseFunction(
359
480
  mergedRouteMap,
@@ -364,9 +485,13 @@ export async function renderStaticSegment<TEnv = any>(
364
485
  };
365
486
 
366
487
  return runWithRequestContext(minimalRequestContext, async () => {
367
- // Static handlers get only reverse and use(handle) — no URL, params,
368
- // request, env, headers, or cookies.
369
- const buildCtx = createStaticContext<TEnv>(mergedRouteMap, routeName);
488
+ // Static handlers get only reverse, use(handle), and optionally env.
489
+ const buildCtx = createStaticContext<TEnv>(
490
+ mergedRouteMap,
491
+ routeName,
492
+ buildEnv,
493
+ devMode,
494
+ );
370
495
 
371
496
  // Set segment ID so handle pushes are keyed correctly
372
497
  (buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
@@ -375,23 +500,41 @@ export async function renderStaticSegment<TEnv = any>(
375
500
  setupBuildUse(buildCtx);
376
501
 
377
502
  const raw = await handler(buildCtx);
378
- const component = raw?.type ? raw : raw;
503
+
504
+ // Static handlers must return a ReactNode. A returned Response (e.g. an
505
+ // accidental redirect()) would otherwise be serialized as a corrupt build
506
+ // artifact; fail loudly instead. The fresh/revalidation paths route handler
507
+ // results through handleHandlerResult, which throws Responses; the static
508
+ // build path bypasses that, so guard here.
509
+ if (raw instanceof Response) {
510
+ throw new TypeError(
511
+ `Static handler "${routeName}" returned a Response. Static handlers must return a ReactNode; ` +
512
+ `Responses (redirects, file responses) are not supported during static pre-rendering.`,
513
+ );
514
+ }
379
515
 
380
516
  const segment: ResolvedSegment = {
381
517
  id: handlerId,
382
518
  namespace: handlerId,
383
519
  type: "layout",
384
520
  index: 0,
385
- component,
521
+ component: raw,
386
522
  params: {},
387
523
  belongsToRoute: false,
388
524
  };
389
525
 
390
526
  const { serializeSegments } = await import("../cache/segment-codec.js");
527
+ const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
391
528
  const [serialized] = await serializeSegments([segment]);
392
529
 
393
- // Collect handle data pushed during rendering
394
- const handles = handleStore.getDataForSegment(handlerId);
530
+ // Collect handle data pushed during rendering and Flight-encode it (so
531
+ // Promise/ReactNode handle values survive the JSON build artifact). "" when
532
+ // nothing was pushed.
533
+ const segHandles = handleStore.getDataForSegment(handlerId);
534
+ const handles =
535
+ Object.keys(segHandles).length > 0
536
+ ? await encodeHandleValue(segHandles)
537
+ : "";
395
538
 
396
539
  return { encoded: serialized.encoded, handles };
397
540
  });
@@ -1,15 +1,9 @@
1
- import { loadManifest } from "./manifest.js";
2
- import { traverseBack } from "./pattern-matching.js";
3
- import { collectRouteMiddleware } from "./middleware.js";
4
- import {
5
- parseAcceptTypes,
6
- RSC_RESPONSE_TYPE,
7
- pickNegotiateVariant,
8
- } from "./content-negotiation.js";
1
+ import { negotiateRoute } from "./content-negotiation.js";
9
2
  import { runWithRouterLogContext, withRouterLogScope } from "./logging.js";
10
3
  import type { EntryData } from "../server/context";
11
4
  import type { RouteMatchResult } from "./pattern-matching.js";
12
5
  import type { MiddlewareFn } from "./middleware.js";
6
+ import { resolveRoute } from "./route-snapshot.js";
13
7
 
14
8
  export interface PreviewMatchDeps<TEnv = any> {
15
9
  findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
@@ -42,110 +36,46 @@ export async function previewMatch<TEnv = any>(
42
36
  const url = new URL(request.url);
43
37
  const pathname = url.pathname;
44
38
 
45
- // Quick route matching
46
- const matched = deps.findMatch(pathname);
47
- if (!matched) {
39
+ // Route resolution via snapshot (lite mode: skip entries/cacheScope
40
+ // since previewMatch only needs matched, manifestEntry, routeMiddleware,
41
+ // and responseType)
42
+ const result = await resolveRoute<TEnv>(pathname, {
43
+ findMatch: deps.findMatch,
44
+ lite: true,
45
+ });
46
+
47
+ if (!result) {
48
48
  return null;
49
49
  }
50
50
 
51
51
  // Skip redirect check - will be handled in full match
52
- if (matched.redirectTo) {
52
+ if (result.type === "redirect") {
53
53
  return { routeMiddleware: undefined };
54
54
  }
55
55
 
56
- // Load manifest (without segment resolution)
57
- const manifestEntry = await loadManifest(
58
- matched.entry,
59
- matched.routeKey,
60
- pathname,
61
- undefined, // No metrics store for preview
62
- false, // isSSR - doesn't matter for preview
63
- );
64
-
65
- // Collect route-level middleware from entry tree
66
- // Includes middleware from orphan layouts (inline layouts within routes)
67
- const routeMiddleware = collectRouteMiddleware(
68
- traverseBack(manifestEntry),
69
- matched.params,
70
- );
71
-
72
- // Check for response type (from trie match or manifest entry)
73
- const responseType =
74
- matched.responseType ||
75
- (manifestEntry.type === "route"
76
- ? manifestEntry.responseType
77
- : undefined);
78
-
79
- // Content negotiation: when negotiate variants exist, pick the best
80
- // handler based on the Accept header. Uses q-values and client order
81
- // as tiebreaker (matching Express/Hono behavior). RSC routes participate
82
- // as text/html candidates so browsers naturally get HTML without
83
- // special-casing.
84
- if (matched.negotiateVariants && matched.negotiateVariants.length > 0) {
85
- const acceptEntries = parseAcceptTypes(
86
- request.headers.get("accept") || "",
87
- );
88
-
89
- // Build candidate list preserving definition order.
90
- // For wildcard (*/*) and no-Accept fallback, the first candidate wins.
91
- const variants = matched.negotiateVariants;
92
- let candidates: Array<{ routeKey: string; responseType: string }>;
93
- if (responseType) {
94
- // Primary is response-type — include it as a candidate
95
- candidates = [
96
- ...variants,
97
- { routeKey: matched.routeKey, responseType },
98
- ];
99
- } else {
100
- // Primary is RSC — insert as text/html candidate in definition order
101
- const rscCandidate = {
102
- routeKey: matched.routeKey,
103
- responseType: RSC_RESPONSE_TYPE,
104
- };
105
- candidates = matched.rscFirst
106
- ? [rscCandidate, ...variants]
107
- : [...variants, rscCandidate];
108
- }
109
-
110
- const variant = pickNegotiateVariant(acceptEntries, candidates);
56
+ const snapshot = result.snapshot;
57
+ const { matched, manifestEntry, routeMiddleware, responseType } =
58
+ snapshot;
111
59
 
112
- // If the winner is RSC, fall through to default RSC handling
113
- if (variant.responseType === RSC_RESPONSE_TYPE) {
114
- // Fall through — RSC won negotiation
115
- } else if (responseType && variant.routeKey === matched.routeKey) {
116
- // Fall through — response-type primary won, already set
117
- } else {
118
- const negotiateEntry = await loadManifest(
119
- matched.entry,
120
- variant.routeKey,
121
- pathname,
122
- undefined,
123
- false,
124
- );
125
- // Recompute middleware from the selected variant's entry tree
126
- // since different variants can have different middleware chains.
127
- const variantMiddleware = collectRouteMiddleware(
128
- traverseBack(negotiateEntry),
129
- matched.params,
130
- );
131
- return {
132
- routeMiddleware:
133
- variantMiddleware.length > 0 ? variantMiddleware : undefined,
134
- responseType: variant.responseType,
135
- handler:
136
- negotiateEntry.type === "route"
137
- ? negotiateEntry.handler
138
- : undefined,
139
- params: matched.params,
140
- negotiated: true,
141
- manifestEntry: negotiateEntry,
142
- routeKey: matched.routeKey,
143
- };
144
- }
60
+ const negotiation = await negotiateRoute(request, pathname, snapshot);
61
+ if (negotiation) {
62
+ return {
63
+ routeMiddleware:
64
+ negotiation.routeMiddleware.length > 0
65
+ ? negotiation.routeMiddleware
66
+ : undefined,
67
+ responseType: negotiation.responseType,
68
+ handler: negotiation.handler,
69
+ params: matched.params,
70
+ manifestEntry: negotiation.manifestEntry,
71
+ routeKey: matched.routeKey,
72
+ // omitted unless a variant negotiated, preserving the prior public
73
+ // shape (absent for plain response routes, not negotiated:false)
74
+ ...(negotiation.negotiated ? { negotiated: true } : {}),
75
+ };
145
76
  }
146
77
 
147
- // If we passed through the negotiation block (variants exist), mark as
148
- // negotiated so the handler sets Vary: Accept on the response.
78
+ // No negotiation or RSC won — return default route info
149
79
  const hasVariants =
150
80
  matched.negotiateVariants && matched.negotiateVariants.length > 0;
151
81
  return {