@rangojs/router 0.0.0-experimental.32 → 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 +120 -204
  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 +190 -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 +63 -24
  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 +338 -126
  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
@@ -1,10 +1,3 @@
1
- /**
2
- * Match API
3
- *
4
- * Extracted from createRouter closure. Contains match context creation functions
5
- * and the matchError function for error boundary resolution.
6
- */
7
-
8
1
  import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
9
2
  import { RouteNotFoundError } from "../errors";
10
3
  import {
@@ -22,10 +15,10 @@ import { collectRouteMiddleware } from "./middleware.js";
22
15
  import { traverseBack } from "./pattern-matching.js";
23
16
  import { DefaultErrorFallback } from "../default-error-boundary.js";
24
17
  import {
25
- EntryData,
26
- LoaderEntry,
18
+ type EntryData,
19
+ type LoaderEntry,
27
20
  getContext,
28
- InterceptSelectorContext,
21
+ type InterceptSelectorContext,
29
22
  } from "../server/context";
30
23
  import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
31
24
  import type { ReactNode } from "react";
@@ -36,7 +29,14 @@ import {
36
29
  setRequestContextPrevRouteKey,
37
30
  } from "../server/request-context.js";
38
31
  import { isAutoGeneratedRouteName } from "../route-name.js";
32
+ import type { DefaultRouteName } from "../types/global-namespace.js";
39
33
  import { debugLog, debugWarn } from "./logging.js";
34
+ import {
35
+ resolveRoute,
36
+ ensureFullRouteSnapshot,
37
+ type RouteSnapshot,
38
+ } from "./route-snapshot.js";
39
+ import { resolveNavigation } from "./navigation-snapshot.js";
40
40
 
41
41
  /**
42
42
  * Create match context for full requests (document/SSR).
@@ -52,58 +52,33 @@ export async function createMatchContextForFull<TEnv>(
52
52
 
53
53
  const metricsStore = deps.getMetricsStore();
54
54
 
55
- const routeMatchStart = metricsStore ? performance.now() : 0;
56
- const matched = deps.findMatch(pathname, metricsStore);
57
- if (metricsStore) {
58
- metricsStore.metrics.push({
59
- label: "route-matching",
60
- duration: performance.now() - routeMatchStart,
61
- startTime: routeMatchStart - metricsStore.requestStart,
62
- });
63
- }
55
+ const result = await resolveRoute<TEnv>(pathname, {
56
+ findMatch: (p) => deps.findMatch(p, metricsStore),
57
+ metricsStore,
58
+ isSSR: true,
59
+ });
64
60
 
65
- if (!matched) {
61
+ if (!result) {
66
62
  throw new RouteNotFoundError(`No route matched for ${pathname}`, {
67
63
  cause: { pathname, method: request.method },
68
64
  });
69
65
  }
70
66
 
71
- if (matched.redirectTo) {
67
+ if (result.type === "redirect") {
72
68
  return {
73
69
  type: "redirect",
74
- redirectUrl: matched.redirectTo + url.search,
70
+ redirectUrl: result.redirectTo + url.search,
75
71
  };
76
72
  }
77
73
 
78
- const manifestStart = metricsStore ? performance.now() : 0;
79
- const manifestEntry = await loadManifest(
80
- matched.entry,
81
- matched.routeKey,
82
- pathname,
83
- metricsStore,
84
- true,
85
- );
86
- if (metricsStore) {
87
- metricsStore.metrics.push({
88
- label: "manifest-loading",
89
- duration: performance.now() - manifestStart,
90
- startTime: manifestStart - metricsStore.requestStart,
91
- });
92
- }
74
+ const snapshot = result.snapshot;
93
75
 
94
- if (
95
- manifestEntry.type === "route" &&
96
- manifestEntry.prerenderDef?.options?.passthrough === true
97
- ) {
76
+ const { matched } = snapshot;
77
+
78
+ if (snapshot.isPassthrough) {
98
79
  matched.pt = true;
99
80
  }
100
81
 
101
- const routeMiddleware = collectRouteMiddleware(
102
- traverseBack(manifestEntry),
103
- matched.params,
104
- );
105
-
106
- // Clean URL without internal _rsc* params for userland access
107
82
  const cleanUrl = stripInternalParams(url);
108
83
 
109
84
  const handlerContext = createHandlerContext(
@@ -134,14 +109,6 @@ export async function createMatchContextForFull<TEnv>(
134
109
  Store.metrics = metricsStore;
135
110
  }
136
111
 
137
- const entries = [...traverseBack(manifestEntry)];
138
- let cacheScope: CacheScope | null = null;
139
- for (const entry of entries) {
140
- if (entry.cache) {
141
- cacheScope = createCacheScope(entry.cache, cacheScope);
142
- }
143
- }
144
-
145
112
  return {
146
113
  request,
147
114
  url: cleanUrl,
@@ -154,12 +121,10 @@ export async function createMatchContextForFull<TEnv>(
154
121
  prevParams: {},
155
122
  prevMatch: null,
156
123
  matched,
157
- manifestEntry,
158
- entries,
124
+ manifestEntry: snapshot.manifestEntry,
125
+ entries: snapshot.entries,
159
126
  routeKey: matched.routeKey,
160
- localRouteName: matched.routeKey.includes(".")
161
- ? matched.routeKey.split(".").pop()!
162
- : matched.routeKey,
127
+ localRouteName: snapshot.localRouteName,
163
128
  handlerContext,
164
129
  loaderPromises,
165
130
  routeMap: deps.getRouteMap(),
@@ -175,16 +140,16 @@ export async function createMatchContextForFull<TEnv>(
175
140
  segments: { path: [], ids: [] },
176
141
  toRouteName:
177
142
  matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
178
- ? matched.routeKey
143
+ ? (matched.routeKey as DefaultRouteName)
179
144
  : undefined,
180
145
  },
181
146
  isSameRouteNavigation: false,
182
147
  interceptResult: null,
183
- cacheScope,
148
+ cacheScope: snapshot.cacheScope,
184
149
  isIntercept: false,
185
150
  actionContext: undefined,
186
151
  isAction: false,
187
- routeMiddleware,
152
+ routeMiddleware: snapshot.routeMiddleware,
188
153
  isFullMatch: true,
189
154
  };
190
155
  }
@@ -204,104 +169,80 @@ export async function createMatchContextForPartial<TEnv>(
204
169
 
205
170
  const metricsStore = deps.getMetricsStore();
206
171
 
207
- const clientSegmentIds =
208
- url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
209
- const stale = url.searchParams.get("_rsc_stale") === "true";
210
- const previousUrl =
211
- request.headers.get("X-RSC-Router-Client-Path") ||
212
- request.headers.get("Referer");
213
- const interceptSourceUrl = request.headers.get(
214
- "X-RSC-Router-Intercept-Source",
215
- );
172
+ const isHmr = !!request.headers.get("X-RSC-HMR");
216
173
 
217
174
  // HMR: clear manifest cache so stale handler references are discarded
218
- if (request.headers.get("X-RSC-HMR")) {
175
+ if (isHmr) {
219
176
  clearManifestCache();
220
177
  }
221
178
 
222
- if (!previousUrl) {
223
- return null;
224
- }
225
-
226
- let prevUrl: URL;
227
- try {
228
- prevUrl = new URL(previousUrl, url.origin);
229
- } catch {
230
- return null;
231
- }
179
+ // Reuse the classified snapshot when available and not invalidated by HMR.
180
+ // classifyRequest already called resolveRoute(lite) with isSSR=false, which
181
+ // matches the partial path. On HMR, discard to pick up manifest changes.
182
+ const classifiedRoute = isHmr
183
+ ? undefined
184
+ : getRequestContext()?._classifiedRoute;
185
+
186
+ // Time route matching. On the reuse path, only nav findMatch calls are new
187
+ // (current-route findMatch and manifest-loading were already timed during
188
+ // classifyRequest via its metricsStore). On the fresh path, all findMatch
189
+ // calls are measured together.
190
+ const routeMatchStart = metricsStore ? performance.now() : 0;
232
191
 
233
- let interceptContextUrl: URL;
234
- try {
235
- interceptContextUrl = interceptSourceUrl
236
- ? new URL(interceptSourceUrl, url.origin)
237
- : prevUrl;
238
- } catch {
239
- interceptContextUrl = prevUrl;
240
- }
192
+ let snapshot: RouteSnapshot<TEnv>;
193
+ if (classifiedRoute && classifiedRoute.manifestEntry) {
194
+ snapshot = ensureFullRouteSnapshot(classifiedRoute);
195
+ } else {
196
+ const result = await resolveRoute<TEnv>(pathname, {
197
+ findMatch: (p) => deps.findMatch(p, metricsStore),
198
+ metricsStore,
199
+ isSSR: false,
200
+ skipRouteMatchMetric: true,
201
+ });
241
202
 
242
- const routeMatchStart = metricsStore ? performance.now() : 0;
243
- const prevMatch = deps.findMatch(prevUrl.pathname);
244
- const prevParams = prevMatch?.params || {};
245
- const interceptContextMatch = interceptSourceUrl
246
- ? deps.findMatch(interceptContextUrl.pathname)
247
- : prevMatch;
203
+ if (!result) {
204
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
205
+ cause: { pathname, method: request.method },
206
+ });
207
+ }
248
208
 
249
- const matched = deps.findMatch(pathname, metricsStore);
209
+ if (result.type === "redirect") {
210
+ return null;
211
+ }
250
212
 
251
- if (metricsStore) {
252
- metricsStore.metrics.push({
253
- label: "route-matching",
254
- duration: performance.now() - routeMatchStart,
255
- startTime: routeMatchStart - metricsStore.requestStart,
256
- });
213
+ snapshot = result.snapshot;
257
214
  }
258
215
 
259
- if (!matched) {
260
- throw new RouteNotFoundError(`No route matched for ${pathname}`, {
261
- cause: { pathname, method: request.method, previousUrl },
262
- });
263
- }
216
+ const { matched } = snapshot;
264
217
 
265
- if (matched.redirectTo) {
266
- return null;
218
+ // Backward compat: downstream middleware reads matched.pt
219
+ if (snapshot.isPassthrough) {
220
+ matched.pt = true;
267
221
  }
268
222
 
269
- if (prevMatch && prevMatch.entry !== matched.entry && !matched.pr) {
270
- debugLog("matchPartial", "route group changed", {
271
- from: prevMatch.routeKey,
272
- to: matched.routeKey,
273
- });
223
+ const nav = resolveNavigation(request, url, matched.routeKey, {
224
+ findMatch: deps.findMatch,
225
+ });
226
+ if (!nav) {
227
+ return null;
274
228
  }
275
229
 
276
- const manifestStart = metricsStore ? performance.now() : 0;
277
- const manifestEntry = await loadManifest(
278
- matched.entry,
279
- matched.routeKey,
280
- pathname,
281
- metricsStore,
282
- false,
283
- );
284
230
  if (metricsStore) {
231
+ const isReuse = !!classifiedRoute;
285
232
  metricsStore.metrics.push({
286
- label: "manifest-loading",
287
- duration: performance.now() - manifestStart,
288
- startTime: manifestStart - metricsStore.requestStart,
233
+ label: isReuse ? "route-matching:nav" : "route-matching",
234
+ duration: performance.now() - routeMatchStart,
235
+ startTime: routeMatchStart - metricsStore.requestStart,
289
236
  });
290
237
  }
291
238
 
292
- if (
293
- manifestEntry.type === "route" &&
294
- manifestEntry.prerenderDef?.options?.passthrough === true
295
- ) {
296
- matched.pt = true;
239
+ if (nav.prevMatch && nav.prevMatch.entry !== matched.entry && !matched.pr) {
240
+ debugLog("matchPartial", "route group changed", {
241
+ from: nav.prevMatch.routeKey,
242
+ to: matched.routeKey,
243
+ });
297
244
  }
298
245
 
299
- const routeMiddleware = collectRouteMiddleware(
300
- traverseBack(manifestEntry),
301
- matched.params,
302
- );
303
-
304
- // Clean URL without internal _rsc* params for userland access
305
246
  const cleanUrl = stripInternalParams(url);
306
247
 
307
248
  const handlerContext = createHandlerContext(
@@ -317,9 +258,8 @@ export async function createMatchContextForPartial<TEnv>(
317
258
  matched.pt === true,
318
259
  );
319
260
 
320
- const clientSegmentSet = new Set(clientSegmentIds);
321
261
  debugLog("matchPartial", "client segments", {
322
- segments: Array.from(clientSegmentSet),
262
+ segments: Array.from(nav.clientSegmentSet),
323
263
  });
324
264
 
325
265
  const loaderPromises = new Map<string, Promise<any>>();
@@ -337,100 +277,75 @@ export async function createMatchContextForPartial<TEnv>(
337
277
  Store.metrics = metricsStore;
338
278
  }
339
279
 
340
- const isSameRouteNavigation = !!(
341
- interceptContextMatch && interceptContextMatch.routeKey === matched.routeKey
342
- );
343
-
344
- if (interceptSourceUrl) {
280
+ if (nav.hasInterceptSource) {
345
281
  debugLog("matchPartial.intercept", "intercept context detected", {
346
282
  currentUrl: pathname,
347
- interceptSource: interceptSourceUrl,
348
- contextRoute: interceptContextMatch?.routeKey,
283
+ interceptSource: nav.interceptContextUrl.href,
284
+ contextRoute: nav.interceptContextMatch?.routeKey,
349
285
  currentRoute: matched.routeKey,
350
- sameRouteNavigation: isSameRouteNavigation,
286
+ sameRouteNavigation: nav.isSameRouteNavigation,
351
287
  });
352
288
  }
353
289
 
354
- const localRouteName = matched.routeKey.includes(".")
355
- ? matched.routeKey.split(".").pop()!
356
- : matched.routeKey;
357
-
358
- const filteredSegmentIds = clientSegmentIds.filter((id) => {
359
- if (id.includes(".@")) return false;
360
- if (/D\d+\./.test(id)) return false;
361
- return true;
362
- });
363
- const effectiveFromUrl = interceptSourceUrl ? interceptContextUrl : prevUrl;
364
- const effectiveFromMatch = interceptSourceUrl
365
- ? interceptContextMatch
366
- : prevMatch;
367
-
368
- // Store previous route key on the request context for revalidation
369
- // fromRouteName. Uses effectiveFromMatch so intercept-source navigations
370
- // see the intercept origin route, not the plain previous URL route.
371
- setRequestContextPrevRouteKey(effectiveFromMatch?.routeKey);
290
+ setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
372
291
 
373
292
  const interceptSelectorContext: InterceptSelectorContext = {
374
- from: effectiveFromUrl,
293
+ from: nav.effectiveFromUrl,
375
294
  to: cleanUrl,
376
295
  params: matched.params,
377
296
  request,
378
297
  env,
379
298
  segments: {
380
- path: effectiveFromUrl.pathname.split("/").filter(Boolean),
381
- ids: filteredSegmentIds,
299
+ path: nav.effectiveFromUrl.pathname.split("/").filter(Boolean),
300
+ ids: nav.filteredSegmentIds,
382
301
  },
383
302
  fromRouteName:
384
- effectiveFromMatch?.routeKey &&
385
- !isAutoGeneratedRouteName(effectiveFromMatch.routeKey)
386
- ? effectiveFromMatch.routeKey
303
+ nav.effectiveFromMatch?.routeKey &&
304
+ !isAutoGeneratedRouteName(nav.effectiveFromMatch.routeKey)
305
+ ? (nav.effectiveFromMatch.routeKey as DefaultRouteName)
387
306
  : undefined,
388
307
  toRouteName:
389
308
  matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
390
- ? matched.routeKey
309
+ ? (matched.routeKey as DefaultRouteName)
391
310
  : undefined,
392
311
  };
393
312
  const isAction = !!actionContext;
394
313
 
395
- const clientHasInterceptSegments = [...clientSegmentSet].some((id) =>
314
+ const clientHasInterceptSegments = [...nav.clientSegmentSet].some((id) =>
396
315
  id.includes(".@"),
397
316
  );
398
317
  const skipInterceptForAction = isAction && !clientHasInterceptSegments;
399
318
  const interceptResult =
400
- isSameRouteNavigation || skipInterceptForAction
319
+ nav.isSameRouteNavigation || skipInterceptForAction
401
320
  ? null
402
321
  : findInterceptForRoute(
403
322
  matched.routeKey,
404
- manifestEntry.parent,
323
+ snapshot.manifestEntry.parent,
405
324
  interceptSelectorContext,
406
325
  isAction,
407
326
  ) ||
408
- (localRouteName !== matched.routeKey
327
+ (snapshot.localRouteName !== matched.routeKey
409
328
  ? findInterceptForRoute(
410
- localRouteName,
411
- manifestEntry.parent,
329
+ snapshot.localRouteName,
330
+ snapshot.manifestEntry.parent,
412
331
  interceptSelectorContext,
413
332
  isAction,
414
333
  )
415
334
  : null);
416
335
 
336
+ // Make a mutable copy of clientSegmentSet so we can delete entries
337
+ // for same-route navigation forcing
338
+ const clientSegmentSet = new Set(nav.clientSegmentSet);
339
+
417
340
  if (
418
- isSameRouteNavigation &&
419
- manifestEntry.type === "route" &&
420
- interceptSourceUrl
341
+ nav.isSameRouteNavigation &&
342
+ snapshot.manifestEntry.type === "route" &&
343
+ nav.hasInterceptSource
421
344
  ) {
422
345
  debugLog("matchPartial.intercept", "forcing route segment render", {
423
- segmentId: manifestEntry.shortCode,
346
+ segmentId: snapshot.manifestEntry.shortCode,
424
347
  });
425
- clientSegmentSet.delete(manifestEntry.shortCode);
426
- }
427
-
428
- const entries = [...traverseBack(manifestEntry)];
429
- let cacheScope: CacheScope | null = null;
430
- for (const entry of entries) {
431
- if (entry.cache) {
432
- cacheScope = createCacheScope(entry.cache, cacheScope);
433
- }
348
+ clientSegmentSet.delete(snapshot.manifestEntry.shortCode);
434
349
  }
435
350
 
436
351
  const isIntercept = !!interceptResult;
@@ -440,31 +355,31 @@ export async function createMatchContextForPartial<TEnv>(
440
355
  url: cleanUrl,
441
356
  pathname,
442
357
  env,
443
- clientSegmentIds,
358
+ clientSegmentIds: nav.clientSegmentIds,
444
359
  clientSegmentSet,
445
- stale,
446
- prevUrl,
447
- prevParams,
448
- prevMatch,
360
+ stale: nav.stale,
361
+ prevUrl: nav.prevUrl,
362
+ prevParams: nav.prevParams,
363
+ prevMatch: nav.prevMatch,
449
364
  matched,
450
- manifestEntry,
451
- entries,
365
+ manifestEntry: snapshot.manifestEntry,
366
+ entries: snapshot.entries,
452
367
  routeKey: matched.routeKey,
453
- localRouteName,
368
+ localRouteName: snapshot.localRouteName,
454
369
  handlerContext,
455
370
  loaderPromises,
456
371
  routeMap: deps.getRouteMap(),
457
372
  metricsStore,
458
373
  Store,
459
- interceptContextMatch,
374
+ interceptContextMatch: nav.interceptContextMatch,
460
375
  interceptSelectorContext,
461
- isSameRouteNavigation,
376
+ isSameRouteNavigation: nav.isSameRouteNavigation,
462
377
  interceptResult,
463
- cacheScope,
378
+ cacheScope: snapshot.cacheScope,
464
379
  isIntercept,
465
380
  actionContext,
466
381
  isAction,
467
- routeMiddleware,
382
+ routeMiddleware: snapshot.routeMiddleware,
468
383
  isFullMatch: false,
469
384
  };
470
385
  }
@@ -615,6 +530,7 @@ export async function matchError<TEnv>(
615
530
  segments: [errorSegment],
616
531
  matched: matchedIds,
617
532
  diff: [errorSegment.id],
533
+ resolvedIds: [errorSegment.id],
618
534
  params: matched.params,
619
535
  };
620
536
  }
@@ -242,25 +242,3 @@ export function createPipelineState(): MatchPipelineState {
242
242
  slots: {},
243
243
  };
244
244
  }
245
-
246
- /**
247
- * Input parameters for createMatchContext
248
- */
249
- export interface CreateMatchContextInput<TEnv = any> {
250
- request: Request;
251
- env: TEnv;
252
- actionContext?: ActionContext;
253
- }
254
-
255
- /**
256
- * Result from createMatchContext - either a context or null (fall back to full match)
257
- */
258
- export type CreateMatchContextResult<TEnv = any> =
259
- | { type: "context"; ctx: MatchContext<TEnv> }
260
- | { type: "fallback"; reason: string }
261
- | { type: "error"; error: Error };
262
-
263
- // Note: createMatchContext() will be implemented in Step J10 when we wire everything together.
264
- // It requires access to RouterContext (findMatch, loadManifest, etc.) which are closure
265
- // functions from createRouter(). The implementation will live in router.ts initially
266
- // and call getRouterContext() to access these dependencies.