@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. 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";
@@ -59,11 +59,16 @@ export async function matchForPrerender<TEnv = any>(
59
59
  devMode?: boolean,
60
60
  ): Promise<{
61
61
  segments: SerializedSegmentData[];
62
- 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;
63
66
  routeName: string;
64
67
  params: Record<string, string>;
65
68
  interceptSegments?: SerializedSegmentData[];
66
- 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;
67
72
  passthrough?: true;
68
73
  } | null> {
69
74
  // 1. Find the matching route entry
@@ -77,6 +82,17 @@ export async function matchForPrerender<TEnv = any>(
77
82
  // Build RouterContext for loadManifest/traverseBack
78
83
  const routerCtx = deps.buildRouterContext();
79
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
+
80
96
  return runWithRouterContext(routerCtx, async () => {
81
97
  // 2. Load the manifest entry tree
82
98
  const manifestEntry = await loadManifest(
@@ -126,7 +142,7 @@ export async function matchForPrerender<TEnv = any>(
126
142
  get env() {
127
143
  if (buildEnv !== undefined) return buildEnv;
128
144
  throw new Error(
129
- "[rsc-router] ctx.env is not available during dev-mode getParams(). " +
145
+ "[rango] ctx.env is not available during dev-mode getParams(). " +
130
146
  "Configure buildEnv in your rango() plugin options to enable build-time env access.",
131
147
  );
132
148
  },
@@ -140,19 +156,10 @@ export async function matchForPrerender<TEnv = any>(
140
156
  );
141
157
  });
142
158
  if (!isKnown) {
143
- return {
144
- segments: [],
145
- handles: {},
146
- routeName: matched.routeKey,
147
- params: matchedParams,
148
- passthrough: true as const,
149
- };
159
+ return passthroughResult();
150
160
  }
151
161
  // Preserve vars set by getParams() for the render context
152
- if (
153
- Object.keys(probeBuildVars).length > 0 ||
154
- Object.getOwnPropertySymbols(probeBuildVars).length > 0
155
- ) {
162
+ if (hasContextVars(probeBuildVars)) {
156
163
  devProbeBuildVars = probeBuildVars;
157
164
  }
158
165
  } catch (err: any) {
@@ -160,13 +167,7 @@ export async function matchForPrerender<TEnv = any>(
160
167
  // Skip errors are intentional — treat as passthrough.
161
168
  // All other errors propagate so dev surfaces them.
162
169
  if (err?.name === "Skip") {
163
- return {
164
- segments: [],
165
- handles: {},
166
- routeName: matched.routeKey,
167
- params: matchedParams,
168
- passthrough: true as const,
169
- };
170
+ return passthroughResult();
170
171
  }
171
172
  throw err;
172
173
  }
@@ -206,11 +207,14 @@ export async function matchForPrerender<TEnv = any>(
206
207
  header: () => {},
207
208
  setStatus: () => {},
208
209
  _setStatus: () => {},
210
+ _rotateStateCookie: () => {},
211
+ _setKeepCacheDirective: () => {},
209
212
  use: (() => {
210
213
  throw new Error("use() not available during pre-rendering");
211
214
  }) as any,
212
215
  method: "GET",
213
216
  _handleStore: handleStore,
217
+ _requestTags: new Set<string>(),
214
218
  waitUntil: () => {},
215
219
  onResponse: () => {},
216
220
  _onResponseCallbacks: [],
@@ -256,17 +260,13 @@ export async function matchForPrerender<TEnv = any>(
256
260
  { skipLoaders: true },
257
261
  );
258
262
 
259
- // 9. Detect passthrough sentinel: handler returned ctx.passthrough()
260
- for (const seg of allSegments) {
261
- if (isPrerenderPassthrough(seg.component)) {
262
- return {
263
- segments: [],
264
- handles: {},
265
- routeName: matched.routeKey,
266
- params: matchedParams,
267
- passthrough: true as const,
268
- };
269
- }
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();
270
270
  }
271
271
 
272
272
  // 10. Filter out any loader segments (belt-and-suspenders)
@@ -278,16 +278,22 @@ export async function matchForPrerender<TEnv = any>(
278
278
 
279
279
  // 12. Serialize segments using the cache serializer
280
280
  const { serializeSegments } = await import("../cache/segment-codec.js");
281
+ const { encodeHandles } = await import("../cache/handle-snapshot.js");
281
282
  const serializedSegments = await serializeSegments(nonLoaderSegments);
282
283
 
283
- // 13. Collect handle data per segment (skip segments with no handle data)
284
- 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> = {};
285
290
  for (const seg of nonLoaderSegments) {
286
291
  const segHandles = handleStore.getDataForSegment(seg.id);
287
292
  if (Object.keys(segHandles).length > 0) {
288
- handles[seg.id] = segHandles;
293
+ handlesRecord[seg.id] = segHandles;
289
294
  }
290
295
  }
296
+ const handles = await encodeHandles(handlesRecord);
291
297
 
292
298
  // Use the trie-level route key (e.g., "docs", "docs.article")
293
299
  const routeName = matched.routeKey;
@@ -297,7 +303,7 @@ export async function matchForPrerender<TEnv = any>(
297
303
  // evaluation -- we pre-render all intercepts unconditionally and let
298
304
  // runtime matching decide which to serve.
299
305
  let interceptSegments: SerializedSegmentData[] | undefined;
300
- let interceptHandles: Record<string, SegmentHandleData> | undefined;
306
+ let interceptHandles: string | undefined;
301
307
 
302
308
  const foundIntercepts: {
303
309
  intercept: InterceptEntry;
@@ -305,24 +311,14 @@ export async function matchForPrerender<TEnv = any>(
305
311
  }[] = [];
306
312
  let current: EntryData | null = manifestEntry;
307
313
  while (current) {
308
- if (current.intercept && current.intercept.length > 0) {
309
- 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) {
310
320
  if (ic.routeName === matched.routeKey) {
311
- foundIntercepts.push({ intercept: ic, entry: current });
312
- }
313
- }
314
- }
315
- if (current.layout && current.layout.length > 0) {
316
- for (const siblingLayout of current.layout) {
317
- if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
318
- for (const ic of siblingLayout.intercept) {
319
- if (ic.routeName === matched.routeKey) {
320
- foundIntercepts.push({
321
- intercept: ic,
322
- entry: siblingLayout,
323
- });
324
- }
325
- }
321
+ foundIntercepts.push({ intercept: ic, entry: source });
326
322
  }
327
323
  }
328
324
  }
@@ -333,6 +329,18 @@ export async function matchForPrerender<TEnv = any>(
333
329
  const interceptResolvedSegments: typeof nonLoaderSegments = [];
334
330
 
335
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
+
336
344
  // Resolve handler
337
345
  const handlerRaw =
338
346
  typeof intercept.handler === "function"
@@ -359,7 +367,7 @@ export async function matchForPrerender<TEnv = any>(
359
367
  }
360
368
 
361
369
  interceptResolvedSegments.push({
362
- id: `${parentEntry.shortCode}.${intercept.slotName}`,
370
+ id: interceptSegmentId,
363
371
  namespace: `intercept:${intercept.routeName}`,
364
372
  type: "parallel" as const,
365
373
  index: 0,
@@ -379,13 +387,20 @@ export async function matchForPrerender<TEnv = any>(
379
387
  interceptSegments = await serializeSegments(
380
388
  interceptResolvedSegments,
381
389
  );
382
- interceptHandles = {};
390
+ const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
383
391
  for (const seg of interceptResolvedSegments) {
384
392
  const segHandles = handleStore.getDataForSegment(seg.id);
385
393
  if (Object.keys(segHandles).length > 0) {
386
- interceptHandles[seg.id] = segHandles;
394
+ interceptHandlesRecord[seg.id] = segHandles;
387
395
  }
388
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
+ });
389
404
  }
390
405
  }
391
406
 
@@ -414,7 +429,7 @@ export async function renderStaticSegment<TEnv = any>(
414
429
  routeName?: string,
415
430
  buildEnv?: TEnv,
416
431
  devMode?: boolean,
417
- ): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null> {
432
+ ): Promise<{ encoded: string; handles: string } | null> {
418
433
  const syntheticUrl = new URL("http://prerender/");
419
434
  const syntheticRequest = new Request(syntheticUrl);
420
435
 
@@ -442,11 +457,14 @@ export async function renderStaticSegment<TEnv = any>(
442
457
  header: () => {},
443
458
  setStatus: () => {},
444
459
  _setStatus: () => {},
460
+ _rotateStateCookie: () => {},
461
+ _setKeepCacheDirective: () => {},
445
462
  use: (() => {
446
463
  throw new Error("use() not available during static pre-rendering");
447
464
  }) as any,
448
465
  method: "GET",
449
466
  _handleStore: handleStore,
467
+ _requestTags: new Set<string>(),
450
468
  waitUntil: () => {},
451
469
  onResponse: () => {},
452
470
  _onResponseCallbacks: [],
@@ -479,23 +497,41 @@ export async function renderStaticSegment<TEnv = any>(
479
497
  setupBuildUse(buildCtx);
480
498
 
481
499
  const raw = await handler(buildCtx);
482
- 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
+ }
483
512
 
484
513
  const segment: ResolvedSegment = {
485
514
  id: handlerId,
486
515
  namespace: handlerId,
487
516
  type: "layout",
488
517
  index: 0,
489
- component,
518
+ component: raw,
490
519
  params: {},
491
520
  belongsToRoute: false,
492
521
  };
493
522
 
494
523
  const { serializeSegments } = await import("../cache/segment-codec.js");
524
+ const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
495
525
  const [serialized] = await serializeSegments([segment]);
496
526
 
497
- // Collect handle data pushed during rendering
498
- 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
+ : "";
499
535
 
500
536
  return { encoded: serialized.encoded, handles };
501
537
  });
@@ -67,9 +67,11 @@ export async function previewMatch<TEnv = any>(
67
67
  responseType: negotiation.responseType,
68
68
  handler: negotiation.handler,
69
69
  params: matched.params,
70
- negotiated: true,
71
70
  manifestEntry: negotiation.manifestEntry,
72
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 } : {}),
73
75
  };
74
76
  }
75
77
 
@@ -23,10 +23,6 @@ import { negotiateRoute } from "./content-negotiation.js";
23
23
  import { stripInternalParams } from "./handler-context.js";
24
24
  import { resolveRoute, type RouteSnapshot } from "./route-snapshot.js";
25
25
 
26
- // ---------------------------------------------------------------------------
27
- // RequestPlan — discriminated union
28
- // ---------------------------------------------------------------------------
29
-
30
26
  interface RedirectPlan<TEnv = any> {
31
27
  mode: "redirect";
32
28
  route: RouteSnapshot<TEnv>;
@@ -40,6 +36,12 @@ interface VersionMismatchPlan<TEnv = any> {
40
36
  reloadUrl: string;
41
37
  }
42
38
 
39
+ interface AppSwitchReloadPlan {
40
+ mode: "app-switch";
41
+ /** Clean target URL (internal _rsc_* params stripped) to navigate to. */
42
+ reloadUrl: string;
43
+ }
44
+
43
45
  interface ResponseRoutePlan<TEnv = any> {
44
46
  mode: "response";
45
47
  route: RouteSnapshot<TEnv>;
@@ -86,6 +88,7 @@ interface PartialRenderPlan<TEnv = any> {
86
88
  export type RequestPlan<TEnv = any> =
87
89
  | RedirectPlan<TEnv>
88
90
  | VersionMismatchPlan<TEnv>
91
+ | AppSwitchReloadPlan
89
92
  | ResponseRoutePlan<TEnv>
90
93
  | LoaderFetchPlan<TEnv>
91
94
  | PeRenderPlan<TEnv>
@@ -94,12 +97,13 @@ export type RequestPlan<TEnv = any> =
94
97
  | PartialRenderPlan<TEnv>;
95
98
 
96
99
  /**
97
- * Plans that have passed the terminal-check gate (version-mismatch handled)
98
- * and are ready for execution. Always have a `route` field.
100
+ * Plans that have passed the terminal-check gate (version-mismatch and
101
+ * app-switch reloads handled) and are ready for execution. Always have a
102
+ * `route` field.
99
103
  */
100
104
  export type ExecutableRequestPlan<TEnv = any> = Exclude<
101
105
  RequestPlan<TEnv>,
102
- VersionMismatchPlan<TEnv>
106
+ VersionMismatchPlan<TEnv> | AppSwitchReloadPlan
103
107
  >;
104
108
 
105
109
  /**
@@ -116,10 +120,6 @@ export type {
116
120
  PartialRenderPlan,
117
121
  };
118
122
 
119
- // ---------------------------------------------------------------------------
120
- // classifyRequest — the single authoritative classification step
121
- // ---------------------------------------------------------------------------
122
-
123
123
  export interface ClassifyRequestDeps<TEnv = any> {
124
124
  findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
125
125
  routerVersion: string;
@@ -149,15 +149,12 @@ export async function classifyRequest<TEnv = any>(
149
149
  const isAction =
150
150
  request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
151
151
 
152
- // Version mismatch — runs BEFORE route resolution so stale clients
153
- // requesting removed routes get a reload, not a 404.
154
152
  const clientVersion = url.searchParams.get("_rsc_v");
155
153
  if (
156
154
  deps.routerVersion &&
157
155
  clientVersion &&
158
156
  clientVersion !== deps.routerVersion
159
157
  ) {
160
- // Strip internal _rsc_* params so the browser reloads to a clean URL
161
158
  let reloadUrl = stripInternalParams(url).toString();
162
159
  if (isAction) {
163
160
  const referer = request.headers.get("referer");
@@ -167,9 +164,7 @@ export async function classifyRequest<TEnv = any>(
167
164
  if (refererUrl.origin === url.origin) {
168
165
  reloadUrl = referer;
169
166
  }
170
- } catch {
171
- // Malformed referer, fall back to stripped url
172
- }
167
+ } catch {}
173
168
  }
174
169
  }
175
170
 
@@ -179,9 +174,18 @@ export async function classifyRequest<TEnv = any>(
179
174
  };
180
175
  }
181
176
 
182
- // No metricsStore — classification is a lightweight gating step.
183
- // Route-matching and manifest-loading metrics belong in the match path
184
- // (createMatchContextForFull/Partial) which runs the authoritative resolution.
177
+ const clientRouterId = url.searchParams.get("_rsc_rid");
178
+ if (
179
+ clientRouterId &&
180
+ clientRouterId !== deps.routerId &&
181
+ url.searchParams.has("_rsc_partial")
182
+ ) {
183
+ return {
184
+ mode: "app-switch",
185
+ reloadUrl: stripInternalParams(url).toString(),
186
+ };
187
+ }
188
+
185
189
  const result = await resolveRoute<TEnv>(pathname, {
186
190
  findMatch: deps.findMatch,
187
191
  lite: true,
@@ -193,7 +197,6 @@ export async function classifyRequest<TEnv = any>(
193
197
  });
194
198
  }
195
199
 
196
- // Redirect
197
200
  if (result.type === "redirect") {
198
201
  const snapshot: RouteSnapshot<TEnv> = {
199
202
  matched: result as any,
@@ -215,7 +218,6 @@ export async function classifyRequest<TEnv = any>(
215
218
 
216
219
  const snapshot = result.snapshot;
217
220
 
218
- // Response route — non-RSC short-circuit (JSON, streaming, etc.)
219
221
  const responseResult = await classifyResponseRoute(
220
222
  request,
221
223
  pathname,
@@ -225,7 +227,6 @@ export async function classifyRequest<TEnv = any>(
225
227
  return responseResult;
226
228
  }
227
229
 
228
- // Mode detection from request signals
229
230
  const actionId =
230
231
  request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
231
232
  const isLoaderFetch = url.searchParams.has("_rsc_loader");
@@ -243,7 +244,6 @@ export async function classifyRequest<TEnv = any>(
243
244
  return { mode: "loader", route: snapshot };
244
245
  }
245
246
 
246
- // PE detection: POST with form content-type, but not a server action
247
247
  const contentType = request.headers.get("content-type") || "";
248
248
  const isFormSubmission =
249
249
  contentType.includes("multipart/form-data") ||
@@ -252,22 +252,13 @@ export async function classifyRequest<TEnv = any>(
252
252
  return { mode: "pe-render", route: snapshot };
253
253
  }
254
254
 
255
- // App switch: client's routerId doesn't match this router
256
- const clientRouterId = url.searchParams.get("_rsc_rid");
257
- const isAppSwitch = !!(clientRouterId && clientRouterId !== deps.routerId);
258
- const isPartial = url.searchParams.has("_rsc_partial") && !isAppSwitch;
259
-
260
- if (isPartial) {
255
+ if (url.searchParams.has("_rsc_partial")) {
261
256
  return { mode: "partial-render", route: snapshot, negotiated };
262
257
  }
263
258
 
264
259
  return { mode: "full-render", route: snapshot, negotiated };
265
260
  }
266
261
 
267
- // ---------------------------------------------------------------------------
268
- // Content negotiation for response routes
269
- // ---------------------------------------------------------------------------
270
-
271
262
  /**
272
263
  * Check if the route is a response route and perform content negotiation
273
264
  * if negotiate variants exist. Returns a ResponseRoutePlan if the route
@@ -278,33 +269,8 @@ async function classifyResponseRoute<TEnv>(
278
269
  pathname: string,
279
270
  snapshot: RouteSnapshot<TEnv>,
280
271
  ): Promise<ResponseRoutePlan<TEnv> | null> {
281
- const { manifestEntry, responseType } = snapshot;
282
-
283
272
  const negotiation = await negotiateRoute(request, pathname, snapshot);
284
- if (negotiation) {
285
- return {
286
- mode: "response",
287
- route: snapshot,
288
- ...negotiation,
289
- };
290
- }
291
-
292
- // Non-negotiated response route (no variants, or RSC won negotiation)
293
- if (responseType) {
294
- const handler =
295
- manifestEntry.type === "route" ? manifestEntry.handler : undefined;
296
- if (handler) {
297
- return {
298
- mode: "response",
299
- route: snapshot,
300
- handler,
301
- responseType,
302
- negotiated: false,
303
- manifestEntry,
304
- routeMiddleware: snapshot.routeMiddleware,
305
- };
306
- }
307
- }
308
-
309
- return null;
273
+ return negotiation
274
+ ? { mode: "response", route: snapshot, ...negotiation }
275
+ : null;
310
276
  }