@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -6,43 +6,44 @@
6
6
  */
7
7
 
8
8
  import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
9
- import {
10
- RouteNotFoundError,
11
- } from "../errors";
9
+ import { RouteNotFoundError } from "../errors";
12
10
  import {
13
11
  createErrorInfo,
14
12
  createErrorSegment,
15
13
  findNearestErrorBoundary as findErrorBoundary,
16
14
  } from "./error-handling.js";
17
- import { createHandlerContext } from "./handler-context.js";
15
+ import {
16
+ createHandlerContext,
17
+ stripInternalParams,
18
+ } from "./handler-context.js";
18
19
  import { setupLoaderAccess } from "./loader-resolution.js";
19
20
  import { loadManifest, clearManifestCache } from "./manifest.js";
20
21
  import { collectRouteMiddleware } from "./middleware.js";
21
22
  import { traverseBack } from "./pattern-matching.js";
22
23
  import { DefaultErrorFallback } from "../default-error-boundary.js";
23
24
  import {
24
- EntryData,
25
- LoaderEntry,
25
+ type EntryData,
26
+ type LoaderEntry,
26
27
  getContext,
27
- InterceptSelectorContext,
28
- type MetricsStore,
28
+ type InterceptSelectorContext,
29
29
  } from "../server/context";
30
- import {
31
- getGlobalRouteMap,
32
- } from "../route-map-builder.js";
33
- import type {
34
- ErrorBoundaryHandler,
35
- ErrorInfo,
36
- HandlerContext,
37
- MatchResult,
38
- ResolvedSegment,
39
- } from "../types";
30
+ import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
40
31
  import type { ReactNode } from "react";
41
- import type { MatchContext, ActionContext as MatchActionContext } from "./match-context.js";
32
+ import type { MatchContext } from "./match-context.js";
42
33
  import type { MatchApiDeps, ActionContext } from "./types.js";
43
- import type { InterceptEntry } from "../server/context";
44
- import type { RouteMatchResult } from "./pattern-matching.js";
45
- import { getRequestContext } from "../server/request-context.js";
34
+ import {
35
+ getRequestContext,
36
+ setRequestContextPrevRouteKey,
37
+ } from "../server/request-context.js";
38
+ import { isAutoGeneratedRouteName } from "../route-name.js";
39
+ import type { DefaultRouteName } from "../types/global-namespace.js";
40
+ import { debugLog, debugWarn } from "./logging.js";
41
+ import {
42
+ resolveRoute,
43
+ ensureFullRouteSnapshot,
44
+ type RouteSnapshot,
45
+ } from "./route-snapshot.js";
46
+ import { resolveNavigation } from "./navigation-snapshot.js";
46
47
 
47
48
  /**
48
49
  * Create match context for full requests (document/SSR).
@@ -58,61 +59,50 @@ export async function createMatchContextForFull<TEnv>(
58
59
 
59
60
  const metricsStore = deps.getMetricsStore();
60
61
 
61
- const routeMatchStart = metricsStore ? performance.now() : 0;
62
- const matched = deps.findMatch(pathname, metricsStore);
63
- if (metricsStore) {
64
- metricsStore.metrics.push({
65
- label: "route-matching",
66
- duration: performance.now() - routeMatchStart,
67
- startTime: routeMatchStart - metricsStore.requestStart,
68
- });
69
- }
62
+ // Full renders always resolve fresh with isSSR: true because loadManifest
63
+ // keys its cache on isSSR and stamps Store.isSSR for downstream behavior.
64
+ const result = await resolveRoute<TEnv>(pathname, {
65
+ findMatch: (p) => deps.findMatch(p, metricsStore),
66
+ metricsStore,
67
+ isSSR: true,
68
+ });
70
69
 
71
- if (!matched) {
70
+ if (!result) {
72
71
  throw new RouteNotFoundError(`No route matched for ${pathname}`, {
73
72
  cause: { pathname, method: request.method },
74
73
  });
75
74
  }
76
75
 
77
- if (matched.redirectTo) {
76
+ if (result.type === "redirect") {
78
77
  return {
79
78
  type: "redirect",
80
- redirectUrl: matched.redirectTo + url.search,
79
+ redirectUrl: result.redirectTo + url.search,
81
80
  };
82
81
  }
83
82
 
84
- const manifestStart = metricsStore ? performance.now() : 0;
85
- const manifestEntry = await loadManifest(
86
- matched.entry,
87
- matched.routeKey,
88
- pathname,
89
- metricsStore,
90
- true,
91
- );
92
- if (metricsStore) {
93
- metricsStore.metrics.push({
94
- label: "manifest-loading",
95
- duration: performance.now() - manifestStart,
96
- startTime: manifestStart - metricsStore.requestStart,
97
- });
98
- }
83
+ const snapshot = result.snapshot;
99
84
 
100
- const routeMiddleware = collectRouteMiddleware(
101
- traverseBack(manifestEntry),
102
- matched.params,
103
- );
85
+ const { matched } = snapshot;
86
+
87
+ // Backward compat: downstream middleware reads matched.pt
88
+ if (snapshot.isPassthrough) {
89
+ matched.pt = true;
90
+ }
104
91
 
105
- const bindings = (env as any)?.Bindings ?? env;
92
+ // Clean URL without internal _rsc* params for userland access
93
+ const cleanUrl = stripInternalParams(url);
106
94
 
107
95
  const handlerContext = createHandlerContext(
108
96
  matched.params,
109
97
  request,
110
- url.searchParams,
98
+ cleanUrl.searchParams,
111
99
  pathname,
112
- url,
113
- bindings,
114
- getGlobalRouteMap(),
100
+ cleanUrl,
101
+ env,
102
+ deps.getRouteMap(),
115
103
  matched.routeKey,
104
+ matched.responseType,
105
+ matched.pt === true,
116
106
  );
117
107
 
118
108
  const loaderPromises = new Map<string, Promise<any>>();
@@ -130,54 +120,47 @@ export async function createMatchContextForFull<TEnv>(
130
120
  Store.metrics = metricsStore;
131
121
  }
132
122
 
133
- const entries = [...traverseBack(manifestEntry)];
134
- let cacheScope: CacheScope | null = null;
135
- for (const entry of entries) {
136
- if (entry.cache) {
137
- cacheScope = createCacheScope(entry.cache, cacheScope);
138
- }
139
- }
140
-
141
123
  return {
142
124
  request,
143
- url,
125
+ url: cleanUrl,
144
126
  pathname,
145
127
  env,
146
- bindings,
147
128
  clientSegmentIds: [],
148
129
  clientSegmentSet: new Set(),
149
130
  stale: false,
150
- prevUrl: url,
131
+ prevUrl: cleanUrl,
151
132
  prevParams: {},
152
133
  prevMatch: null,
153
134
  matched,
154
- manifestEntry,
155
- entries,
135
+ manifestEntry: snapshot.manifestEntry,
136
+ entries: snapshot.entries,
156
137
  routeKey: matched.routeKey,
157
- localRouteName: matched.routeKey.includes(".")
158
- ? matched.routeKey.split(".").pop()!
159
- : matched.routeKey,
138
+ localRouteName: snapshot.localRouteName,
160
139
  handlerContext,
161
140
  loaderPromises,
162
- routeMap: getGlobalRouteMap(),
141
+ routeMap: deps.getRouteMap(),
163
142
  metricsStore,
164
143
  Store,
165
144
  interceptContextMatch: null,
166
145
  interceptSelectorContext: {
167
- from: url,
168
- to: url,
146
+ from: cleanUrl,
147
+ to: cleanUrl,
169
148
  params: matched.params,
170
149
  request,
171
150
  env,
172
151
  segments: { path: [], ids: [] },
152
+ toRouteName:
153
+ matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
154
+ ? (matched.routeKey as DefaultRouteName)
155
+ : undefined,
173
156
  },
174
157
  isSameRouteNavigation: false,
175
158
  interceptResult: null,
176
- cacheScope,
159
+ cacheScope: snapshot.cacheScope,
177
160
  isIntercept: false,
178
161
  actionContext: undefined,
179
162
  isAction: false,
180
- routeMiddleware,
163
+ routeMiddleware: snapshot.routeMiddleware,
181
164
  isFullMatch: true,
182
165
  };
183
166
  }
@@ -195,104 +178,106 @@ export async function createMatchContextForPartial<TEnv>(
195
178
  const url = new URL(request.url);
196
179
  const pathname = url.pathname;
197
180
 
198
- const requestStartTime = performance.now();
199
181
  const metricsStore = deps.getMetricsStore();
200
182
 
201
- const clientSegmentIds =
202
- url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
203
- const stale = url.searchParams.get("_rsc_stale") === "true";
204
- const previousUrl =
205
- request.headers.get("X-RSC-Router-Client-Path") ||
206
- request.headers.get("Referer");
207
- const interceptSourceUrl = request.headers.get(
208
- "X-RSC-Router-Intercept-Source",
209
- );
183
+ const isHmr = !!request.headers.get("X-RSC-HMR");
210
184
 
211
185
  // HMR: clear manifest cache so stale handler references are discarded
212
- if (request.headers.get("X-RSC-HMR")) {
186
+ if (isHmr) {
213
187
  clearManifestCache();
214
188
  }
215
189
 
216
- if (!previousUrl) {
217
- return null;
218
- }
190
+ // Reuse the classified snapshot when available and not invalidated by HMR.
191
+ // classifyRequest already called resolveRoute(lite) with isSSR=false, which
192
+ // matches the partial path. On HMR, discard to pick up manifest changes.
193
+ const classifiedRoute = isHmr
194
+ ? undefined
195
+ : getRequestContext()?._classifiedRoute;
196
+
197
+ // Time route matching. On the reuse path, only nav findMatch calls are new
198
+ // (current-route findMatch and manifest-loading were already timed during
199
+ // classifyRequest via its metricsStore). On the fresh path, all findMatch
200
+ // calls are measured together.
201
+ const routeMatchStart = metricsStore ? performance.now() : 0;
219
202
 
220
- const prevUrl = new URL(previousUrl, url.origin);
221
- const interceptContextUrl = interceptSourceUrl
222
- ? new URL(interceptSourceUrl, url.origin)
223
- : prevUrl;
203
+ let snapshot: RouteSnapshot<TEnv>;
204
+ if (classifiedRoute && classifiedRoute.manifestEntry) {
205
+ snapshot = ensureFullRouteSnapshot(classifiedRoute);
206
+ } else {
207
+ const result = await resolveRoute<TEnv>(pathname, {
208
+ findMatch: (p) => deps.findMatch(p, metricsStore),
209
+ metricsStore,
210
+ isSSR: false,
211
+ skipRouteMatchMetric: true,
212
+ });
224
213
 
225
- const routeMatchStart = metricsStore ? performance.now() : 0;
226
- const prevMatch = deps.findMatch(prevUrl.pathname);
227
- const prevParams = prevMatch?.params || {};
228
- const interceptContextMatch = interceptSourceUrl
229
- ? deps.findMatch(interceptContextUrl.pathname)
230
- : prevMatch;
214
+ if (!result) {
215
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
216
+ cause: { pathname, method: request.method },
217
+ });
218
+ }
231
219
 
232
- const matched = deps.findMatch(pathname, metricsStore);
220
+ if (result.type === "redirect") {
221
+ return null;
222
+ }
233
223
 
234
- if (metricsStore) {
235
- metricsStore.metrics.push({
236
- label: "route-matching",
237
- duration: performance.now() - routeMatchStart,
238
- startTime: routeMatchStart - metricsStore.requestStart,
239
- });
224
+ snapshot = result.snapshot;
240
225
  }
241
226
 
242
- if (!matched) {
243
- throw new RouteNotFoundError(`No route matched for ${pathname}`, {
244
- cause: { pathname, method: request.method, previousUrl },
245
- });
246
- }
227
+ const { matched } = snapshot;
247
228
 
248
- if (matched.redirectTo) {
249
- return null;
229
+ // Backward compat: downstream middleware reads matched.pt
230
+ if (snapshot.isPassthrough) {
231
+ matched.pt = true;
250
232
  }
251
233
 
252
- if (prevMatch && prevMatch.entry !== matched.entry && !matched.pr) {
253
- console.log(
254
- `[Router.matchPartial] Route group changed: ${prevMatch.routeKey} → ${matched.routeKey}`,
255
- );
234
+ // Navigation state (prev + intercept-source findMatch calls)
235
+ const nav = resolveNavigation(request, url, matched.routeKey, {
236
+ findMatch: deps.findMatch,
237
+ });
238
+ if (!nav) {
239
+ return null;
256
240
  }
257
241
 
258
- const manifestStart = metricsStore ? performance.now() : 0;
259
- const manifestEntry = await loadManifest(
260
- matched.entry,
261
- matched.routeKey,
262
- pathname,
263
- metricsStore,
264
- false,
265
- );
242
+ // Push route-matching metric. On the fresh path this covers all findMatch
243
+ // calls (current + prev + intercept-source). On the reuse path, current-route
244
+ // findMatch was already timed during classification, so this only covers
245
+ // the nav lookups (prev + intercept-source).
266
246
  if (metricsStore) {
247
+ const isReuse = !!classifiedRoute;
267
248
  metricsStore.metrics.push({
268
- label: "manifest-loading",
269
- duration: performance.now() - manifestStart,
270
- startTime: manifestStart - metricsStore.requestStart,
249
+ label: isReuse ? "route-matching:nav" : "route-matching",
250
+ duration: performance.now() - routeMatchStart,
251
+ startTime: routeMatchStart - metricsStore.requestStart,
271
252
  });
272
253
  }
273
254
 
274
- const routeMiddleware = collectRouteMiddleware(
275
- traverseBack(manifestEntry),
276
- matched.params,
277
- );
255
+ if (nav.prevMatch && nav.prevMatch.entry !== matched.entry && !matched.pr) {
256
+ debugLog("matchPartial", "route group changed", {
257
+ from: nav.prevMatch.routeKey,
258
+ to: matched.routeKey,
259
+ });
260
+ }
261
+
262
+ // Clean URL without internal _rsc* params for userland access
263
+ const cleanUrl = stripInternalParams(url);
278
264
 
279
- const bindings = (env as any)?.Bindings ?? env;
280
265
  const handlerContext = createHandlerContext(
281
266
  matched.params,
282
267
  request,
283
- url.searchParams,
268
+ cleanUrl.searchParams,
284
269
  pathname,
285
- url,
286
- bindings,
287
- getGlobalRouteMap(),
270
+ cleanUrl,
271
+ env,
272
+ deps.getRouteMap(),
288
273
  matched.routeKey,
274
+ matched.responseType,
275
+ matched.pt === true,
289
276
  );
290
277
 
291
- const clientSegmentSet = new Set(clientSegmentIds);
292
- console.log(
293
- `[Router.matchPartial] Client segments:`,
294
- Array.from(clientSegmentSet),
295
- );
278
+ debugLog("matchPartial", "client segments", {
279
+ segments: Array.from(nav.clientSegmentSet),
280
+ });
296
281
 
297
282
  const loaderPromises = new Map<string, Promise<any>>();
298
283
  setupLoaderAccess(handlerContext, loaderPromises);
@@ -309,116 +294,112 @@ export async function createMatchContextForPartial<TEnv>(
309
294
  Store.metrics = metricsStore;
310
295
  }
311
296
 
312
- const isSameRouteNavigation = !!(
313
- interceptContextMatch &&
314
- interceptContextMatch.routeKey === matched.routeKey
315
- );
316
-
317
- if (interceptSourceUrl) {
318
- console.log(`[Router.matchPartial] Intercept context detected:`);
319
- console.log(` - Current URL: ${pathname}`);
320
- console.log(` - Intercept source: ${interceptSourceUrl}`);
321
- console.log(` - Context match: ${interceptContextMatch?.routeKey}`);
322
- console.log(` - Current route: ${matched.routeKey}`);
323
- console.log(` - Same route navigation: ${isSameRouteNavigation}`);
297
+ if (nav.hasInterceptSource) {
298
+ debugLog("matchPartial.intercept", "intercept context detected", {
299
+ currentUrl: pathname,
300
+ interceptSource: nav.interceptContextUrl.href,
301
+ contextRoute: nav.interceptContextMatch?.routeKey,
302
+ currentRoute: matched.routeKey,
303
+ sameRouteNavigation: nav.isSameRouteNavigation,
304
+ });
324
305
  }
325
306
 
326
- const localRouteName = matched.routeKey.includes(".")
327
- ? matched.routeKey.split(".").pop()!
328
- : matched.routeKey;
307
+ // Store previous route key on the request context for revalidation
308
+ // fromRouteName. Uses effectiveFromMatch so intercept-source navigations
309
+ // see the intercept origin route, not the plain previous URL route.
310
+ setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
329
311
 
330
- const filteredSegmentIds = clientSegmentIds.filter((id) => {
331
- if (id.includes(".@")) return false;
332
- if (/D\d+\./.test(id)) return false;
333
- return true;
334
- });
335
312
  const interceptSelectorContext: InterceptSelectorContext = {
336
- from: prevUrl,
337
- to: url,
313
+ from: nav.effectiveFromUrl,
314
+ to: cleanUrl,
338
315
  params: matched.params,
339
316
  request,
340
317
  env,
341
318
  segments: {
342
- path: prevUrl.pathname.split("/").filter(Boolean),
343
- ids: filteredSegmentIds,
319
+ path: nav.effectiveFromUrl.pathname.split("/").filter(Boolean),
320
+ ids: nav.filteredSegmentIds,
344
321
  },
322
+ fromRouteName:
323
+ nav.effectiveFromMatch?.routeKey &&
324
+ !isAutoGeneratedRouteName(nav.effectiveFromMatch.routeKey)
325
+ ? (nav.effectiveFromMatch.routeKey as DefaultRouteName)
326
+ : undefined,
327
+ toRouteName:
328
+ matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
329
+ ? (matched.routeKey as DefaultRouteName)
330
+ : undefined,
345
331
  };
346
332
  const isAction = !!actionContext;
347
333
 
348
- const clientHasInterceptSegments = [...clientSegmentSet].some((id) =>
334
+ const clientHasInterceptSegments = [...nav.clientSegmentSet].some((id) =>
349
335
  id.includes(".@"),
350
336
  );
351
337
  const skipInterceptForAction = isAction && !clientHasInterceptSegments;
352
338
  const interceptResult =
353
- isSameRouteNavigation || skipInterceptForAction
339
+ nav.isSameRouteNavigation || skipInterceptForAction
354
340
  ? null
355
341
  : findInterceptForRoute(
356
342
  matched.routeKey,
357
- manifestEntry.parent,
343
+ snapshot.manifestEntry.parent,
358
344
  interceptSelectorContext,
359
345
  isAction,
360
346
  ) ||
361
- (localRouteName !== matched.routeKey
347
+ (snapshot.localRouteName !== matched.routeKey
362
348
  ? findInterceptForRoute(
363
- localRouteName,
364
- manifestEntry.parent,
349
+ snapshot.localRouteName,
350
+ snapshot.manifestEntry.parent,
365
351
  interceptSelectorContext,
366
352
  isAction,
367
353
  )
368
354
  : null);
369
355
 
356
+ // Make a mutable copy of clientSegmentSet so we can delete entries
357
+ // for same-route navigation forcing
358
+ const clientSegmentSet = new Set(nav.clientSegmentSet);
359
+
370
360
  if (
371
- isSameRouteNavigation &&
372
- manifestEntry.type === "route" &&
373
- interceptSourceUrl
361
+ nav.isSameRouteNavigation &&
362
+ snapshot.manifestEntry.type === "route" &&
363
+ nav.hasInterceptSource
374
364
  ) {
375
- console.log(
376
- `[Router.matchPartial] Leaving intercept - forcing route segment render: ${manifestEntry.shortCode}`,
377
- );
378
- clientSegmentSet.delete(manifestEntry.shortCode);
379
- }
380
-
381
- const entries = [...traverseBack(manifestEntry)];
382
- let cacheScope: CacheScope | null = null;
383
- for (const entry of entries) {
384
- if (entry.cache) {
385
- cacheScope = createCacheScope(entry.cache, cacheScope);
386
- }
365
+ debugLog("matchPartial.intercept", "forcing route segment render", {
366
+ segmentId: snapshot.manifestEntry.shortCode,
367
+ });
368
+ clientSegmentSet.delete(snapshot.manifestEntry.shortCode);
387
369
  }
388
370
 
389
371
  const isIntercept = !!interceptResult;
390
372
 
391
373
  return {
392
374
  request,
393
- url,
375
+ url: cleanUrl,
394
376
  pathname,
395
377
  env,
396
- bindings,
397
- clientSegmentIds,
378
+ clientSegmentIds: nav.clientSegmentIds,
398
379
  clientSegmentSet,
399
- stale,
400
- prevUrl,
401
- prevParams,
402
- prevMatch,
380
+ stale: nav.stale,
381
+ prevUrl: nav.prevUrl,
382
+ prevParams: nav.prevParams,
383
+ prevMatch: nav.prevMatch,
403
384
  matched,
404
- manifestEntry,
405
- entries,
385
+ manifestEntry: snapshot.manifestEntry,
386
+ entries: snapshot.entries,
406
387
  routeKey: matched.routeKey,
407
- localRouteName,
388
+ localRouteName: snapshot.localRouteName,
408
389
  handlerContext,
409
390
  loaderPromises,
410
- routeMap: getGlobalRouteMap(),
391
+ routeMap: deps.getRouteMap(),
411
392
  metricsStore,
412
393
  Store,
413
- interceptContextMatch,
394
+ interceptContextMatch: nav.interceptContextMatch,
414
395
  interceptSelectorContext,
415
- isSameRouteNavigation,
396
+ isSameRouteNavigation: nav.isSameRouteNavigation,
416
397
  interceptResult,
417
- cacheScope,
398
+ cacheScope: snapshot.cacheScope,
418
399
  isIntercept,
419
400
  actionContext,
420
401
  isAction,
421
- routeMiddleware,
402
+ routeMiddleware: snapshot.routeMiddleware,
422
403
  isFullMatch: false,
423
404
  };
424
405
  }
@@ -437,11 +418,11 @@ export async function matchError<TEnv>(
437
418
  const url = new URL(request.url);
438
419
  const pathname = url.pathname;
439
420
 
440
- console.log(`[Router.matchError] Matching error for ${pathname}`);
421
+ debugLog("matchError", "matching error", { pathname });
441
422
 
442
423
  const matched = deps.findMatch(pathname);
443
424
  if (!matched) {
444
- console.warn(`[Router.matchError] No route matched for ${pathname}`);
425
+ debugWarn("matchError", "no route matched", { pathname });
445
426
  return null;
446
427
  }
447
428
 
@@ -545,10 +526,7 @@ export async function matchError<TEnv>(
545
526
 
546
527
  const reqCtx = getRequestContext();
547
528
  if (reqCtx) {
548
- reqCtx.res = new Response(null, {
549
- status: 500,
550
- headers: reqCtx.res.headers,
551
- });
529
+ reqCtx._setStatus(500);
552
530
  }
553
531
 
554
532
  const effectiveFallback = fallback || DefaultErrorFallback;
@@ -560,62 +538,19 @@ export async function matchError<TEnv>(
560
538
  );
561
539
 
562
540
  if (useDefaultFallback) {
563
- console.log(
564
- `[Router.matchError] Using default error boundary (no user-defined boundary found)`,
565
- );
541
+ debugLog("matchError", "using default error boundary");
566
542
  }
567
543
 
568
- console.log(
569
- `[Router.matchError] Boundary: ${boundaryEntry.shortCode}, outlet replaced: ${outletEntry.shortCode}`,
570
- );
544
+ debugLog("matchError", "resolved boundary", {
545
+ boundarySegmentId: boundaryEntry.shortCode,
546
+ outletSegmentId: outletEntry.shortCode,
547
+ });
571
548
 
572
549
  return {
573
550
  segments: [errorSegment],
574
551
  matched: matchedIds,
575
552
  diff: [errorSegment.id],
553
+ resolvedIds: [errorSegment.id],
576
554
  params: matched.params,
577
555
  };
578
556
  }
579
-
580
- /**
581
- * Preview match - returns route middleware without segment resolution.
582
- */
583
- export async function previewMatch<TEnv>(
584
- request: Request,
585
- context: TEnv,
586
- deps: MatchApiDeps<TEnv>,
587
- ): Promise<{
588
- routeMiddleware?: Array<{
589
- handler: import("./middleware.js").MiddlewareFn;
590
- params: Record<string, string>;
591
- }>;
592
- } | null> {
593
- const url = new URL(request.url);
594
- const pathname = url.pathname;
595
-
596
- const matched = deps.findMatch(pathname);
597
- if (!matched) {
598
- return null;
599
- }
600
-
601
- if (matched.redirectTo) {
602
- return { routeMiddleware: undefined };
603
- }
604
-
605
- const manifestEntry = await loadManifest(
606
- matched.entry,
607
- matched.routeKey,
608
- pathname,
609
- undefined,
610
- false,
611
- );
612
-
613
- const routeMiddleware = collectRouteMiddleware(
614
- traverseBack(manifestEntry),
615
- matched.params,
616
- );
617
-
618
- return {
619
- routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
620
- };
621
- }
@@ -45,7 +45,7 @@
45
45
  * - request, url, pathname: The incoming HTTP request
46
46
  *
47
47
  * Environment:
48
- * - env, bindings: Server environment (Cloudflare bindings, etc.)
48
+ * - env: Server environment (Cloudflare bindings, etc.)
49
49
  *
50
50
  * Client State (from RSC request headers):
51
51
  * - clientSegmentIds: Segments the client currently has
@@ -140,7 +140,6 @@ export interface MatchContext<TEnv = any> {
140
140
 
141
141
  // Environment
142
142
  env: TEnv;
143
- bindings: TEnv;
144
143
 
145
144
  // Client state
146
145
  clientSegmentIds: string[];
@@ -211,6 +210,9 @@ export interface MatchPipelineState {
211
210
  // Whether cache should be revalidated (SWR)
212
211
  shouldRevalidate?: boolean;
213
212
 
213
+ // Source of cache hit ("runtime" or "prerender")
214
+ cacheSource?: "runtime" | "prerender";
215
+
214
216
  // Resolved segments from pipeline
215
217
  segments: ResolvedSegment[];
216
218
  matchedIds: string[];