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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -1,22 +1,24 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { createHandleStore } from "../server/handle-store.js";
3
+ import { resolveSegmentHandleValues } from "../handles/deferred-resolution.js";
3
4
  import { getRequestContext } from "../server/request-context.js";
4
5
  import {
5
6
  runWithRequestContext,
6
7
  type RequestContext,
7
8
  } from "../server/request-context.js";
8
- import { contextGet, contextSet } from "../context-var.js";
9
+ import { contextGet, contextSet, hasContextVars } from "../context-var.js";
9
10
  import {
10
11
  createPrerenderContext,
11
12
  createStaticContext,
12
13
  createReverseFunction,
13
14
  } from "./handler-context.js";
14
- import { isPrerenderPassthrough } from "../prerender.js";
15
+ import { detectPrerenderPassthrough } from "../prerender.js";
15
16
  import { isRouteRootScoped } from "../route-map-builder.js";
16
17
  import { setupBuildUse } from "./loader-resolution.js";
17
18
  import { loadManifest } from "./manifest.js";
18
19
  import { traverseBack } from "./pattern-matching.js";
19
20
  import type { RouterContext } from "./router-context.js";
21
+ import type { ResolveSegmentOptions } from "./segment-resolution.js";
20
22
  import { runWithRouterContext } from "./router-context.js";
21
23
  import type { EntryData, InterceptEntry } from "../server/context";
22
24
  import type {
@@ -31,7 +33,9 @@ import type {
31
33
  import type { RouteMatchResult } from "./pattern-matching.js";
32
34
 
33
35
  export interface PrerenderMatchDeps<TEnv = any> {
34
- findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
36
+ findMatch: (
37
+ pathname: string,
38
+ ) => RouteMatchResult<TEnv> | null | Promise<RouteMatchResult<TEnv> | null>;
35
39
  buildRouterContext: () => RouterContext<TEnv>;
36
40
  mergedRouteMap: Record<string, string>;
37
41
  resolveAllSegments: (
@@ -40,7 +44,7 @@ export interface PrerenderMatchDeps<TEnv = any> {
40
44
  params: Record<string, string>,
41
45
  context: HandlerContext<any, TEnv>,
42
46
  loaderPromises: Map<string, Promise<any>>,
43
- options?: { skipLoaders?: boolean },
47
+ options?: ResolveSegmentOptions,
44
48
  ) => Promise<ResolvedSegment[]>;
45
49
  }
46
50
 
@@ -59,15 +63,20 @@ export async function matchForPrerender<TEnv = any>(
59
63
  devMode?: boolean,
60
64
  ): Promise<{
61
65
  segments: SerializedSegmentData[];
62
- handles: Record<string, SegmentHandleData>;
66
+ /** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. Encoded in
67
+ * the producer (where the Flight codec resolves) so the node-side build/dev
68
+ * sinks can persist it without touching the codec. */
69
+ handles: string;
63
70
  routeName: string;
64
71
  params: Record<string, string>;
65
72
  interceptSegments?: SerializedSegmentData[];
66
- interceptHandles?: Record<string, SegmentHandleData>;
73
+ /** RSC-encoded MERGED (main + intercept) handle map for the intercept artifact;
74
+ * the sinks store it as-is (no longer merge raw records). */
75
+ interceptHandles?: string;
67
76
  passthrough?: true;
68
77
  } | null> {
69
78
  // 1. Find the matching route entry
70
- const matched = deps.findMatch(pathname);
79
+ const matched = await deps.findMatch(pathname);
71
80
  if (!matched) return null;
72
81
 
73
82
  // Use params from trie match if available, fall back to provided params
@@ -77,6 +86,17 @@ export async function matchForPrerender<TEnv = any>(
77
86
  // Build RouterContext for loadManifest/traverseBack
78
87
  const routerCtx = deps.buildRouterContext();
79
88
 
89
+ // Passthrough sentinel result: an unknown-param Passthrough route falls
90
+ // through to the live handler at runtime, so no artifact is baked. A fresh
91
+ // object is returned per call (no site mutates or identity-compares it).
92
+ const passthroughResult = () => ({
93
+ segments: [],
94
+ handles: "",
95
+ routeName: matched.routeKey,
96
+ params: matchedParams,
97
+ passthrough: true as const,
98
+ });
99
+
80
100
  return runWithRouterContext(routerCtx, async () => {
81
101
  // 2. Load the manifest entry tree
82
102
  const manifestEntry = await loadManifest(
@@ -140,19 +160,10 @@ export async function matchForPrerender<TEnv = any>(
140
160
  );
141
161
  });
142
162
  if (!isKnown) {
143
- return {
144
- segments: [],
145
- handles: {},
146
- routeName: matched.routeKey,
147
- params: matchedParams,
148
- passthrough: true as const,
149
- };
163
+ return passthroughResult();
150
164
  }
151
165
  // Preserve vars set by getParams() for the render context
152
- if (
153
- Object.keys(probeBuildVars).length > 0 ||
154
- Object.getOwnPropertySymbols(probeBuildVars).length > 0
155
- ) {
166
+ if (hasContextVars(probeBuildVars)) {
156
167
  devProbeBuildVars = probeBuildVars;
157
168
  }
158
169
  } catch (err: any) {
@@ -160,13 +171,7 @@ export async function matchForPrerender<TEnv = any>(
160
171
  // Skip errors are intentional — treat as passthrough.
161
172
  // All other errors propagate so dev surfaces them.
162
173
  if (err?.name === "Skip") {
163
- return {
164
- segments: [],
165
- handles: {},
166
- routeName: matched.routeKey,
167
- params: matchedParams,
168
- passthrough: true as const,
169
- };
174
+ return passthroughResult();
170
175
  }
171
176
  throw err;
172
177
  }
@@ -206,11 +211,14 @@ export async function matchForPrerender<TEnv = any>(
206
211
  header: () => {},
207
212
  setStatus: () => {},
208
213
  _setStatus: () => {},
214
+ _rotateStateCookie: () => {},
215
+ _setKeepCacheDirective: () => {},
209
216
  use: (() => {
210
217
  throw new Error("use() not available during pre-rendering");
211
218
  }) as any,
212
219
  method: "GET",
213
220
  _handleStore: handleStore,
221
+ _requestTags: new Set<string>(),
214
222
  waitUntil: () => {},
215
223
  onResponse: () => {},
216
224
  _onResponseCallbacks: [],
@@ -253,20 +261,18 @@ export async function matchForPrerender<TEnv = any>(
253
261
  matchedParams,
254
262
  buildCtx,
255
263
  loaderPromises,
256
- { skipLoaders: true },
264
+ // throwOnError: a render failure (or `throw new Skip()`) must reach the
265
+ // build/dev caller, not be baked into a frozen error page (issue #587).
266
+ { skipLoaders: true, throwOnError: true },
257
267
  );
258
268
 
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
- }
269
+ // 9. Detect passthrough sentinel: handler returned ctx.passthrough().
270
+ // When the route declares loading(), the handler result is deferred so the
271
+ // component is a thenable resolving to the sentinel — detectPrerenderPassthrough
272
+ // resolves thenables before testing (a sync check would miss it and bake a
273
+ // corrupt artifact).
274
+ if (await detectPrerenderPassthrough(allSegments)) {
275
+ return passthroughResult();
270
276
  }
271
277
 
272
278
  // 10. Filter out any loader segments (belt-and-suspenders)
@@ -278,16 +284,24 @@ export async function matchForPrerender<TEnv = any>(
278
284
 
279
285
  // 12. Serialize segments using the cache serializer
280
286
  const { serializeSegments } = await import("../cache/segment-codec.js");
287
+ const { encodeHandles } = await import("../cache/handle-snapshot.js");
281
288
  const serializedSegments = await serializeSegments(nonLoaderSegments);
282
289
 
283
- // 13. Collect handle data per segment (skip segments with no handle data)
284
- const handles: Record<string, SegmentHandleData> = {};
290
+ // 13. Collect handle data per segment (skip segments with no handle data).
291
+ // Encoded through the Flight codec (not stored raw) so Promise/ReactNode
292
+ // handle values survive the JSON-serialized build artifact / dev wire —
293
+ // the same fix the runtime cache uses. Encode happens here, in the RSC
294
+ // environment where the codec resolves; the node-side sinks only persist.
295
+ const handlesRecord: Record<string, SegmentHandleData> = {};
285
296
  for (const seg of nonLoaderSegments) {
286
297
  const segHandles = handleStore.getDataForSegment(seg.id);
287
298
  if (Object.keys(segHandles).length > 0) {
288
- handles[seg.id] = segHandles;
299
+ // Resolve deferred values before encoding so the baked artifact holds
300
+ // resolved data (prerender = build-time cache).
301
+ handlesRecord[seg.id] = await resolveSegmentHandleValues(segHandles);
289
302
  }
290
303
  }
304
+ const handles = await encodeHandles(handlesRecord);
291
305
 
292
306
  // Use the trie-level route key (e.g., "docs", "docs.article")
293
307
  const routeName = matched.routeKey;
@@ -297,7 +311,7 @@ export async function matchForPrerender<TEnv = any>(
297
311
  // evaluation -- we pre-render all intercepts unconditionally and let
298
312
  // runtime matching decide which to serve.
299
313
  let interceptSegments: SerializedSegmentData[] | undefined;
300
- let interceptHandles: Record<string, SegmentHandleData> | undefined;
314
+ let interceptHandles: string | undefined;
301
315
 
302
316
  const foundIntercepts: {
303
317
  intercept: InterceptEntry;
@@ -305,24 +319,14 @@ export async function matchForPrerender<TEnv = any>(
305
319
  }[] = [];
306
320
  let current: EntryData | null = manifestEntry;
307
321
  while (current) {
308
- if (current.intercept && current.intercept.length > 0) {
309
- for (const ic of current.intercept) {
322
+ // Flatten the entry and its sibling layouts into one source list, the
323
+ // same traversal findInterceptForRoute uses; the build keeps ALL matches
324
+ // (not just the innermost) and skips when(). intercept/layout are
325
+ // non-optional arrays, so empty ones are a no-op here.
326
+ for (const source of [current, ...current.layout]) {
327
+ for (const ic of source.intercept) {
310
328
  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
- }
329
+ foundIntercepts.push({ intercept: ic, entry: source });
326
330
  }
327
331
  }
328
332
  }
@@ -333,6 +337,18 @@ export async function matchForPrerender<TEnv = any>(
333
337
  const interceptResolvedSegments: typeof nonLoaderSegments = [];
334
338
 
335
339
  for (const { intercept, entry: parentEntry } of foundIntercepts) {
340
+ // setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
341
+ // resolveAllSegments pass left it on the route's segment id, so pin it
342
+ // to THIS intercept's slot id before resolving the handler/layout --
343
+ // otherwise the intercept's ctx.use() handle pushes land in the wrong
344
+ // bucket and getDataForSegment(seg.id) below drops them from the baked
345
+ // artifact. Re-pinned per iteration so multiple intercepts targeting
346
+ // the same route each get their own id (mirrors fresh.ts parallel-slot
347
+ // pinning).
348
+ const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
349
+ (buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
350
+ interceptSegmentId;
351
+
336
352
  // Resolve handler
337
353
  const handlerRaw =
338
354
  typeof intercept.handler === "function"
@@ -359,7 +375,7 @@ export async function matchForPrerender<TEnv = any>(
359
375
  }
360
376
 
361
377
  interceptResolvedSegments.push({
362
- id: `${parentEntry.shortCode}.${intercept.slotName}`,
378
+ id: interceptSegmentId,
363
379
  namespace: `intercept:${intercept.routeName}`,
364
380
  type: "parallel" as const,
365
381
  index: 0,
@@ -379,13 +395,21 @@ export async function matchForPrerender<TEnv = any>(
379
395
  interceptSegments = await serializeSegments(
380
396
  interceptResolvedSegments,
381
397
  );
382
- interceptHandles = {};
398
+ const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
383
399
  for (const seg of interceptResolvedSegments) {
384
400
  const segHandles = handleStore.getDataForSegment(seg.id);
385
401
  if (Object.keys(segHandles).length > 0) {
386
- interceptHandles[seg.id] = segHandles;
402
+ interceptHandlesRecord[seg.id] =
403
+ await resolveSegmentHandleValues(segHandles);
387
404
  }
388
405
  }
406
+ // The intercept artifact serves main + intercept segments together, so
407
+ // encode the MERGED handle map here (the sinks no longer merge raw
408
+ // records — they store this pre-encoded string as-is).
409
+ interceptHandles = await encodeHandles({
410
+ ...handlesRecord,
411
+ ...interceptHandlesRecord,
412
+ });
389
413
  }
390
414
  }
391
415
 
@@ -414,7 +438,7 @@ export async function renderStaticSegment<TEnv = any>(
414
438
  routeName?: string,
415
439
  buildEnv?: TEnv,
416
440
  devMode?: boolean,
417
- ): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null> {
441
+ ): Promise<{ encoded: string; handles: string } | null> {
418
442
  const syntheticUrl = new URL("http://prerender/");
419
443
  const syntheticRequest = new Request(syntheticUrl);
420
444
 
@@ -442,11 +466,14 @@ export async function renderStaticSegment<TEnv = any>(
442
466
  header: () => {},
443
467
  setStatus: () => {},
444
468
  _setStatus: () => {},
469
+ _rotateStateCookie: () => {},
470
+ _setKeepCacheDirective: () => {},
445
471
  use: (() => {
446
472
  throw new Error("use() not available during static pre-rendering");
447
473
  }) as any,
448
474
  method: "GET",
449
475
  _handleStore: handleStore,
476
+ _requestTags: new Set<string>(),
450
477
  waitUntil: () => {},
451
478
  onResponse: () => {},
452
479
  _onResponseCallbacks: [],
@@ -479,23 +506,41 @@ export async function renderStaticSegment<TEnv = any>(
479
506
  setupBuildUse(buildCtx);
480
507
 
481
508
  const raw = await handler(buildCtx);
482
- const component = raw?.type ? raw : raw;
509
+
510
+ // Static handlers must return a ReactNode. A returned Response (e.g. an
511
+ // accidental redirect()) would otherwise be serialized as a corrupt build
512
+ // artifact; fail loudly instead. The fresh/revalidation paths route handler
513
+ // results through handleHandlerResult, which throws Responses; the static
514
+ // build path bypasses that, so guard here.
515
+ if (raw instanceof Response) {
516
+ throw new TypeError(
517
+ `Static handler "${routeName}" returned a Response. Static handlers must return a ReactNode; ` +
518
+ `Responses (redirects, file responses) are not supported during static pre-rendering.`,
519
+ );
520
+ }
483
521
 
484
522
  const segment: ResolvedSegment = {
485
523
  id: handlerId,
486
524
  namespace: handlerId,
487
525
  type: "layout",
488
526
  index: 0,
489
- component,
527
+ component: raw,
490
528
  params: {},
491
529
  belongsToRoute: false,
492
530
  };
493
531
 
494
532
  const { serializeSegments } = await import("../cache/segment-codec.js");
533
+ const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
495
534
  const [serialized] = await serializeSegments([segment]);
496
535
 
497
- // Collect handle data pushed during rendering
498
- const handles = handleStore.getDataForSegment(handlerId);
536
+ // Collect handle data pushed during rendering and Flight-encode it (so
537
+ // Promise/ReactNode handle values survive the JSON build artifact). "" when
538
+ // nothing was pushed.
539
+ const segHandles = handleStore.getDataForSegment(handlerId);
540
+ const handles =
541
+ Object.keys(segHandles).length > 0
542
+ ? await encodeHandleValue(await resolveSegmentHandleValues(segHandles))
543
+ : "";
499
544
 
500
545
  return { encoded: serialized.encoded, handles };
501
546
  });
@@ -6,7 +6,9 @@ import type { MiddlewareFn } from "./middleware.js";
6
6
  import { resolveRoute } from "./route-snapshot.js";
7
7
 
8
8
  export interface PreviewMatchDeps<TEnv = any> {
9
- findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
9
+ findMatch: (
10
+ pathname: string,
11
+ ) => RouteMatchResult<TEnv> | null | Promise<RouteMatchResult<TEnv> | null>;
10
12
  }
11
13
 
12
14
  /**
@@ -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,12 +120,10 @@ 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
- findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
124
+ findMatch: (
125
+ pathname: string,
126
+ ) => RouteMatchResult<TEnv> | null | Promise<RouteMatchResult<TEnv> | null>;
125
127
  routerVersion: string;
126
128
  routerId: string;
127
129
  }
@@ -148,16 +150,30 @@ export async function classifyRequest<TEnv = any>(
148
150
  const pathname = url.pathname;
149
151
  const isAction =
150
152
  request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
153
+ const isLoaderFetch = url.searchParams.has("_rsc_loader");
154
+ const isPartialReq = url.searchParams.has("_rsc_partial");
155
+ const contentType = request.headers.get("content-type") || "";
156
+ const isFormSubmission =
157
+ contentType.includes("multipart/form-data") ||
158
+ contentType.includes("application/x-www-form-urlencoded");
159
+ const isPeRender = request.method === "POST" && !isAction && isFormSubmission;
160
+
161
+ // A plain document request (full-render mode) is the only mode that renders
162
+ // through match() with isSSR:true; every other mode (action, loader, PE,
163
+ // partial) renders — if at all — through the non-SSR matchPartial() path.
164
+ // Resolve the manifest under the flag the eventual render will use so
165
+ // createMatchContextForFull can reuse this snapshot instead of re-resolving.
166
+ // Response routes ignore isSSR entirely (they short-circuit before match),
167
+ // so the flag is moot for them. Mode detection below reuses these booleans.
168
+ const willFullRender =
169
+ !isAction && !isLoaderFetch && !isPartialReq && !isPeRender;
151
170
 
152
- // Version mismatch — runs BEFORE route resolution so stale clients
153
- // requesting removed routes get a reload, not a 404.
154
171
  const clientVersion = url.searchParams.get("_rsc_v");
155
172
  if (
156
173
  deps.routerVersion &&
157
174
  clientVersion &&
158
175
  clientVersion !== deps.routerVersion
159
176
  ) {
160
- // Strip internal _rsc_* params so the browser reloads to a clean URL
161
177
  let reloadUrl = stripInternalParams(url).toString();
162
178
  if (isAction) {
163
179
  const referer = request.headers.get("referer");
@@ -167,9 +183,7 @@ export async function classifyRequest<TEnv = any>(
167
183
  if (refererUrl.origin === url.origin) {
168
184
  reloadUrl = referer;
169
185
  }
170
- } catch {
171
- // Malformed referer, fall back to stripped url
172
- }
186
+ } catch {}
173
187
  }
174
188
  }
175
189
 
@@ -179,12 +193,22 @@ export async function classifyRequest<TEnv = any>(
179
193
  };
180
194
  }
181
195
 
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.
196
+ const clientRouterId = url.searchParams.get("_rsc_rid");
197
+ if (
198
+ clientRouterId &&
199
+ clientRouterId !== deps.routerId &&
200
+ url.searchParams.has("_rsc_partial")
201
+ ) {
202
+ return {
203
+ mode: "app-switch",
204
+ reloadUrl: stripInternalParams(url).toString(),
205
+ };
206
+ }
207
+
185
208
  const result = await resolveRoute<TEnv>(pathname, {
186
209
  findMatch: deps.findMatch,
187
210
  lite: true,
211
+ isSSR: willFullRender,
188
212
  });
189
213
 
190
214
  if (!result) {
@@ -193,7 +217,6 @@ export async function classifyRequest<TEnv = any>(
193
217
  });
194
218
  }
195
219
 
196
- // Redirect
197
220
  if (result.type === "redirect") {
198
221
  const snapshot: RouteSnapshot<TEnv> = {
199
222
  matched: result as any,
@@ -215,7 +238,6 @@ export async function classifyRequest<TEnv = any>(
215
238
 
216
239
  const snapshot = result.snapshot;
217
240
 
218
- // Response route — non-RSC short-circuit (JSON, streaming, etc.)
219
241
  const responseResult = await classifyResponseRoute(
220
242
  request,
221
243
  pathname,
@@ -225,10 +247,8 @@ export async function classifyRequest<TEnv = any>(
225
247
  return responseResult;
226
248
  }
227
249
 
228
- // Mode detection from request signals
229
250
  const actionId =
230
251
  request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
231
- const isLoaderFetch = url.searchParams.has("_rsc_loader");
232
252
 
233
253
  const hasVariants =
234
254
  snapshot.matched.negotiateVariants &&
@@ -243,31 +263,17 @@ export async function classifyRequest<TEnv = any>(
243
263
  return { mode: "loader", route: snapshot };
244
264
  }
245
265
 
246
- // PE detection: POST with form content-type, but not a server action
247
- const contentType = request.headers.get("content-type") || "";
248
- const isFormSubmission =
249
- contentType.includes("multipart/form-data") ||
250
- contentType.includes("application/x-www-form-urlencoded");
251
- if (request.method === "POST" && !isAction && isFormSubmission) {
266
+ if (isPeRender) {
252
267
  return { mode: "pe-render", route: snapshot };
253
268
  }
254
269
 
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) {
270
+ if (isPartialReq) {
261
271
  return { mode: "partial-render", route: snapshot, negotiated };
262
272
  }
263
273
 
264
274
  return { mode: "full-render", route: snapshot, negotiated };
265
275
  }
266
276
 
267
- // ---------------------------------------------------------------------------
268
- // Content negotiation for response routes
269
- // ---------------------------------------------------------------------------
270
-
271
277
  /**
272
278
  * Check if the route is a response route and perform content negotiation
273
279
  * if negotiate variants exist. Returns a ResponseRoutePlan if the route
@@ -278,7 +284,6 @@ async function classifyResponseRoute<TEnv>(
278
284
  pathname: string,
279
285
  snapshot: RouteSnapshot<TEnv>,
280
286
  ): Promise<ResponseRoutePlan<TEnv> | null> {
281
- // negotiateRoute returns the response plan (variant or plain) or null for RSC.
282
287
  const negotiation = await negotiateRoute(request, pathname, snapshot);
283
288
  return negotiation
284
289
  ? { mode: "response", route: snapshot, ...negotiation }