@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17

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