@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c

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 (189) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +172 -50
  3. package/dist/bin/rango.js +138 -50
  4. package/dist/vite/index.js +1160 -508
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +17 -16
  8. package/skills/breadcrumbs/SKILL.md +252 -0
  9. package/skills/cache-guide/SKILL.md +32 -0
  10. package/skills/caching/SKILL.md +49 -8
  11. package/skills/document-cache/SKILL.md +2 -2
  12. package/skills/handler-use/SKILL.md +362 -0
  13. package/skills/hooks/SKILL.md +61 -51
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +20 -0
  16. package/skills/layout/SKILL.md +22 -0
  17. package/skills/links/SKILL.md +91 -17
  18. package/skills/loader/SKILL.md +107 -24
  19. package/skills/middleware/SKILL.md +34 -3
  20. package/skills/migrate-nextjs/SKILL.md +560 -0
  21. package/skills/migrate-react-router/SKILL.md +765 -0
  22. package/skills/parallel/SKILL.md +185 -0
  23. package/skills/prerender/SKILL.md +112 -70
  24. package/skills/rango/SKILL.md +24 -23
  25. package/skills/response-routes/SKILL.md +8 -0
  26. package/skills/route/SKILL.md +58 -4
  27. package/skills/router-setup/SKILL.md +95 -5
  28. package/skills/streams-and-websockets/SKILL.md +283 -0
  29. package/skills/typesafety/SKILL.md +38 -24
  30. package/src/__internal.ts +92 -0
  31. package/src/browser/app-shell.ts +52 -0
  32. package/src/browser/app-version.ts +14 -0
  33. package/src/browser/event-controller.ts +5 -0
  34. package/src/browser/link-interceptor.ts +4 -0
  35. package/src/browser/navigation-bridge.ts +175 -17
  36. package/src/browser/navigation-client.ts +177 -44
  37. package/src/browser/navigation-store.ts +68 -9
  38. package/src/browser/navigation-transaction.ts +11 -9
  39. package/src/browser/partial-update.ts +113 -17
  40. package/src/browser/prefetch/cache.ts +275 -28
  41. package/src/browser/prefetch/fetch.ts +191 -46
  42. package/src/browser/prefetch/policy.ts +6 -0
  43. package/src/browser/prefetch/queue.ts +123 -20
  44. package/src/browser/prefetch/resource-ready.ts +77 -0
  45. package/src/browser/rango-state.ts +53 -13
  46. package/src/browser/react/Link.tsx +98 -14
  47. package/src/browser/react/NavigationProvider.tsx +89 -14
  48. package/src/browser/react/context.ts +7 -2
  49. package/src/browser/react/use-handle.ts +9 -58
  50. package/src/browser/react/use-navigation.ts +22 -2
  51. package/src/browser/react/use-params.ts +11 -1
  52. package/src/browser/react/use-router.ts +29 -9
  53. package/src/browser/rsc-router.tsx +177 -66
  54. package/src/browser/scroll-restoration.ts +41 -42
  55. package/src/browser/segment-reconciler.ts +36 -9
  56. package/src/browser/server-action-bridge.ts +8 -6
  57. package/src/browser/types.ts +73 -5
  58. package/src/build/generate-manifest.ts +6 -6
  59. package/src/build/generate-route-types.ts +3 -0
  60. package/src/build/route-trie.ts +67 -25
  61. package/src/build/route-types/include-resolution.ts +8 -1
  62. package/src/build/route-types/router-processing.ts +223 -74
  63. package/src/build/route-types/scan-filter.ts +8 -1
  64. package/src/cache/cache-runtime.ts +15 -11
  65. package/src/cache/cache-scope.ts +48 -7
  66. package/src/cache/cf/cf-cache-store.ts +455 -15
  67. package/src/cache/cf/index.ts +5 -1
  68. package/src/cache/document-cache.ts +17 -7
  69. package/src/cache/index.ts +1 -0
  70. package/src/cache/taint.ts +55 -0
  71. package/src/client.rsc.tsx +2 -1
  72. package/src/client.tsx +85 -276
  73. package/src/context-var.ts +72 -2
  74. package/src/debug.ts +2 -2
  75. package/src/handle.ts +40 -0
  76. package/src/handles/breadcrumbs.ts +66 -0
  77. package/src/handles/index.ts +1 -0
  78. package/src/host/index.ts +0 -3
  79. package/src/index.rsc.ts +9 -36
  80. package/src/index.ts +79 -70
  81. package/src/outlet-context.ts +1 -1
  82. package/src/prerender/store.ts +57 -15
  83. package/src/prerender.ts +138 -77
  84. package/src/response-utils.ts +28 -0
  85. package/src/reverse.ts +27 -2
  86. package/src/route-definition/dsl-helpers.ts +240 -40
  87. package/src/route-definition/helpers-types.ts +67 -19
  88. package/src/route-definition/index.ts +3 -3
  89. package/src/route-definition/redirect.ts +11 -3
  90. package/src/route-definition/resolve-handler-use.ts +155 -0
  91. package/src/route-map-builder.ts +7 -1
  92. package/src/route-types.ts +18 -0
  93. package/src/router/content-negotiation.ts +100 -1
  94. package/src/router/find-match.ts +4 -2
  95. package/src/router/handler-context.ts +129 -26
  96. package/src/router/intercept-resolution.ts +11 -4
  97. package/src/router/lazy-includes.ts +10 -7
  98. package/src/router/loader-resolution.ts +160 -22
  99. package/src/router/logging.ts +5 -2
  100. package/src/router/manifest.ts +31 -16
  101. package/src/router/match-api.ts +128 -193
  102. package/src/router/match-middleware/background-revalidation.ts +30 -2
  103. package/src/router/match-middleware/cache-lookup.ts +94 -17
  104. package/src/router/match-middleware/cache-store.ts +53 -10
  105. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  106. package/src/router/match-middleware/segment-resolution.ts +61 -5
  107. package/src/router/match-result.ts +103 -18
  108. package/src/router/metrics.ts +238 -13
  109. package/src/router/middleware-types.ts +48 -27
  110. package/src/router/middleware.ts +201 -86
  111. package/src/router/navigation-snapshot.ts +182 -0
  112. package/src/router/pattern-matching.ts +77 -11
  113. package/src/router/prerender-match.ts +114 -10
  114. package/src/router/preview-match.ts +30 -102
  115. package/src/router/request-classification.ts +310 -0
  116. package/src/router/revalidation.ts +27 -7
  117. package/src/router/route-snapshot.ts +245 -0
  118. package/src/router/router-context.ts +6 -1
  119. package/src/router/router-interfaces.ts +50 -5
  120. package/src/router/router-options.ts +50 -19
  121. package/src/router/segment-resolution/fresh.ts +215 -19
  122. package/src/router/segment-resolution/helpers.ts +30 -25
  123. package/src/router/segment-resolution/loader-cache.ts +1 -0
  124. package/src/router/segment-resolution/revalidation.ts +454 -301
  125. package/src/router/segment-wrappers.ts +2 -0
  126. package/src/router/trie-matching.ts +30 -6
  127. package/src/router/types.ts +1 -0
  128. package/src/router/url-params.ts +49 -0
  129. package/src/router.ts +89 -17
  130. package/src/rsc/handler.ts +563 -364
  131. package/src/rsc/helpers.ts +69 -41
  132. package/src/rsc/index.ts +0 -20
  133. package/src/rsc/loader-fetch.ts +23 -3
  134. package/src/rsc/manifest-init.ts +5 -1
  135. package/src/rsc/progressive-enhancement.ts +37 -10
  136. package/src/rsc/response-route-handler.ts +14 -1
  137. package/src/rsc/rsc-rendering.ts +47 -44
  138. package/src/rsc/server-action.ts +24 -10
  139. package/src/rsc/ssr-setup.ts +128 -0
  140. package/src/rsc/types.ts +11 -1
  141. package/src/search-params.ts +16 -13
  142. package/src/segment-content-promise.ts +67 -0
  143. package/src/segment-loader-promise.ts +122 -0
  144. package/src/segment-system.tsx +109 -23
  145. package/src/server/context.ts +174 -19
  146. package/src/server/handle-store.ts +19 -0
  147. package/src/server/loader-registry.ts +9 -8
  148. package/src/server/request-context.ts +218 -65
  149. package/src/server.ts +6 -0
  150. package/src/ssr/index.tsx +4 -0
  151. package/src/static-handler.ts +18 -6
  152. package/src/theme/index.ts +4 -13
  153. package/src/types/cache-types.ts +4 -4
  154. package/src/types/handler-context.ts +140 -72
  155. package/src/types/loader-types.ts +41 -15
  156. package/src/types/request-scope.ts +126 -0
  157. package/src/types/route-config.ts +17 -8
  158. package/src/types/route-entry.ts +19 -1
  159. package/src/types/segments.ts +2 -5
  160. package/src/urls/include-helper.ts +24 -14
  161. package/src/urls/path-helper-types.ts +39 -6
  162. package/src/urls/path-helper.ts +48 -13
  163. package/src/urls/pattern-types.ts +12 -0
  164. package/src/urls/response-types.ts +18 -16
  165. package/src/use-loader.tsx +77 -5
  166. package/src/vite/discovery/bundle-postprocess.ts +61 -89
  167. package/src/vite/discovery/discover-routers.ts +7 -4
  168. package/src/vite/discovery/prerender-collection.ts +162 -88
  169. package/src/vite/discovery/state.ts +17 -13
  170. package/src/vite/index.ts +8 -3
  171. package/src/vite/plugin-types.ts +51 -79
  172. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  173. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  174. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  175. package/src/vite/plugins/expose-action-id.ts +1 -3
  176. package/src/vite/plugins/expose-id-utils.ts +12 -0
  177. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  178. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  179. package/src/vite/plugins/performance-tracks.ts +88 -0
  180. package/src/vite/plugins/refresh-cmd.ts +127 -0
  181. package/src/vite/plugins/version-plugin.ts +13 -1
  182. package/src/vite/rango.ts +190 -217
  183. package/src/vite/router-discovery.ts +241 -45
  184. package/src/vite/utils/banner.ts +4 -4
  185. package/src/vite/utils/package-resolution.ts +34 -1
  186. package/src/vite/utils/prerender-utils.ts +97 -5
  187. package/src/vite/utils/shared-utils.ts +3 -2
  188. package/skills/testing/SKILL.md +0 -226
  189. package/src/route-definition/route-function.ts +0 -119
@@ -204,6 +204,7 @@ export function createSegmentWrappers<TEnv = any>(
204
204
  interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
205
205
  localRouteName: string,
206
206
  pathname: string,
207
+ stale?: boolean,
207
208
  ): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
208
209
  return _resolveAllSegmentsWithRevalidation(
209
210
  entries,
@@ -221,6 +222,7 @@ export function createSegmentWrappers<TEnv = any>(
221
222
  localRouteName,
222
223
  pathname,
223
224
  segmentDeps,
225
+ stale,
224
226
  );
225
227
  }
226
228
 
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { TrieNode, TrieLeaf } from "../build/route-trie.js";
9
+ import { safeDecodeURIComponent } from "./url-params.js";
9
10
 
10
11
  export interface TrieMatchResult {
11
12
  /** Route name */
@@ -114,7 +115,25 @@ function walkTrie(
114
115
  if (result) return result;
115
116
  }
116
117
 
117
- // Priority 2: Param match
118
+ // Priority 2: Suffix-param match (e.g., :productId.html)
119
+ if (node.xp) {
120
+ for (const suffix in node.xp) {
121
+ if (segment.endsWith(suffix) && segment.length > suffix.length) {
122
+ const paramValue = segment.slice(0, -suffix.length);
123
+ paramValues.push(paramValue);
124
+ const result = walkTrie(
125
+ node.xp[suffix].c,
126
+ segments,
127
+ index + 1,
128
+ paramValues,
129
+ );
130
+ paramValues.pop();
131
+ if (result) return result;
132
+ }
133
+ }
134
+ }
135
+
136
+ // Priority 3: Param match
118
137
  if (node.p) {
119
138
  paramValues.push(segment);
120
139
  const result = walkTrie(node.p.c, segments, index + 1, paramValues);
@@ -122,7 +141,7 @@ function walkTrie(
122
141
  if (result) return result;
123
142
  }
124
143
 
125
- // Priority 3: Wildcard match (consumes rest)
144
+ // Priority 4: Wildcard match (consumes rest)
126
145
  if (node.w) {
127
146
  const rest = joinRemainingSegments(segments, index);
128
147
  return {
@@ -155,20 +174,25 @@ function validateAndBuild(
155
174
  originalPathname: string,
156
175
  pathnameHasTrailingSlash: boolean,
157
176
  ): TrieMatchResult | null {
158
- // Build named params by zipping leaf.pa with positional paramValues
177
+ // Build named params by zipping leaf.pa with positional paramValues.
178
+ // Params are URL-decoded at this boundary so ctx.params holds the values
179
+ // apps expect (matching Express/React Router) and round-trip cleanly
180
+ // through ctx.reverse.
159
181
  const params: Record<string, string> = {};
160
182
  if (leaf.pa) {
161
183
  for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
162
- params[leaf.pa[i]] = paramValues[i];
184
+ params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
163
185
  }
164
186
  }
165
187
 
166
188
  // Add wildcard param (wildcard leaves have pn from TrieNode.w type)
167
189
  if (wildcardValue !== undefined && "pn" in leaf) {
168
- params[(leaf as TrieLeaf & { pn: string }).pn] = wildcardValue;
190
+ params[(leaf as TrieLeaf & { pn: string }).pn] =
191
+ safeDecodeURIComponent(wildcardValue);
169
192
  }
170
193
 
171
- // Validate constraints
194
+ // Validate constraints against decoded values so constraint lists can be
195
+ // written in decoded form (e.g. ["en-GB", "en US"]).
172
196
  if (leaf.cv) {
173
197
  for (const paramName in leaf.cv) {
174
198
  const allowed = leaf.cv[paramName]!;
@@ -96,6 +96,7 @@ export interface SegmentResolutionDeps<TEnv = any> {
96
96
  findNearestNotFoundBoundary: (
97
97
  entry: EntryData | null,
98
98
  ) => ReactNode | NotFoundBoundaryHandler | null;
99
+ notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
99
100
  callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
100
101
  }
101
102
 
@@ -0,0 +1,49 @@
1
+ /**
2
+ * URL param encode/decode at the route boundary.
3
+ *
4
+ * Extraction (decode): regex/trie matchers keep param values URL-encoded;
5
+ * `safeDecodeURIComponent` turns them back into raw strings so `ctx.params`
6
+ * matches the contract apps expect (Express/React Router/Fastify/Koa) and
7
+ * round-trips through reverse stay stable. Malformed %-encoding is
8
+ * preserved as-is so a broken URL doesn't crash matching.
9
+ *
10
+ * Reversal (encode): `encodePathSegment` escapes only what RFC 3986
11
+ * requires for a path segment — `/`, `?`, `#`, space, control chars,
12
+ * non-ASCII — and leaves pchar sub-delims (`@ : $ & + , ; =` and friends)
13
+ * readable. `encodeURIComponent` over-encodes for path segments, which
14
+ * makes generated URLs harder for humans to read in the address bar
15
+ * (e.g. mailbox IDs like `ivo@example.com` would become
16
+ * `ivo%40example.com` even though `@` is path-legal).
17
+ */
18
+
19
+ export function safeDecodeURIComponent(raw: string): string {
20
+ if (raw === "" || raw.indexOf("%") === -1) return raw;
21
+ try {
22
+ return decodeURIComponent(raw);
23
+ } catch {
24
+ return raw;
25
+ }
26
+ }
27
+
28
+ // encodeURIComponent over-encodes for path segments. After running it,
29
+ // un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
30
+ // keeps human-readable characters that are legal in a path segment.
31
+ // Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
32
+ // encoded.
33
+ const PATH_SAFE_ESCAPES: Record<string, string> = {
34
+ "%3A": ":",
35
+ "%40": "@",
36
+ "%24": "$",
37
+ "%26": "&",
38
+ "%2B": "+",
39
+ "%2C": ",",
40
+ "%3B": ";",
41
+ "%3D": "=",
42
+ };
43
+
44
+ export function encodePathSegment(value: string): string {
45
+ return encodeURIComponent(value).replace(
46
+ /%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
47
+ (match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match,
48
+ );
49
+ }
package/src/router.ts CHANGED
@@ -19,9 +19,10 @@ import {
19
19
  import MapRootLayout from "./server/root-layout.js";
20
20
  import type { AllUseItems } from "./route-types.js";
21
21
  import type { UrlPatterns } from "./urls.js";
22
+ import type { UrlBuilder } from "./urls/pattern-types.js";
23
+ import { urls } from "./urls.js";
22
24
  import {
23
- EntryData,
24
- InterceptSelectorContext,
25
+ type EntryData,
25
26
  getContext,
26
27
  RSCRouterContext,
27
28
  type MetricsStore,
@@ -133,6 +134,7 @@ export function createRouter<TEnv = any>(
133
134
  const {
134
135
  id: userProvidedId,
135
136
  $$id: injectedId,
137
+ basename: basenameOption,
136
138
  debugPerformance = false,
137
139
  document: documentOption,
138
140
  defaultErrorBoundary,
@@ -147,7 +149,7 @@ export function createRouter<TEnv = any>(
147
149
  $$sourceFile: injectedSourceFile,
148
150
  nonce,
149
151
  version,
150
- prefetchCacheControl: prefetchCacheControlOption,
152
+ prefetchCacheTTL: prefetchCacheTTLOption,
151
153
  warmup: warmupOption,
152
154
  allowDebugManifest: allowDebugManifestOption = false,
153
155
  telemetry: telemetrySink,
@@ -158,6 +160,13 @@ export function createRouter<TEnv = any>(
158
160
  originCheck: originCheckOption,
159
161
  } = options;
160
162
 
163
+ // Normalize basename: ensure leading slash, strip trailing slash.
164
+ // A bare "/" is equivalent to no basename.
165
+ const basename =
166
+ basenameOption && basenameOption.replace(/^\/+|\/+$/g, "")
167
+ ? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
168
+ : undefined;
169
+
161
170
  // Resolve telemetry sink (no-op when not configured)
162
171
  const telemetry = resolveSink(telemetrySink);
163
172
 
@@ -200,11 +209,17 @@ export function createRouter<TEnv = any>(
200
209
  const routerId =
201
210
  userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
202
211
 
203
- // Resolve prefetch cache control (default: 'private, max-age=300')
204
- const prefetchCacheControl =
205
- prefetchCacheControlOption !== undefined
206
- ? prefetchCacheControlOption
207
- : "private, max-age=300";
212
+ // Resolve prefetch cache TTL (default: 300 seconds / 5 minutes)
213
+ // Clamp to a non-negative integer for valid Cache-Control max-age.
214
+ const rawTTL =
215
+ prefetchCacheTTLOption !== undefined ? prefetchCacheTTLOption : 300;
216
+ const prefetchCacheTTLSeconds =
217
+ rawTTL === false ? 0 : Math.max(0, Math.floor(rawTTL));
218
+ const prefetchCacheTTL = prefetchCacheTTLSeconds * 1000;
219
+ const prefetchCacheControl: string | false =
220
+ prefetchCacheTTLSeconds === 0
221
+ ? false
222
+ : `private, max-age=${prefetchCacheTTLSeconds}`;
208
223
 
209
224
  // Resolve warmup enabled flag (default: true)
210
225
  const warmupEnabled = warmupOption !== false;
@@ -357,8 +372,18 @@ export function createRouter<TEnv = any>(
357
372
  return precomputedByPrefix;
358
373
  }
359
374
 
360
- // Wrapper to pass debugPerformance to external createMetricsStore
361
- const getMetricsStore = () => createMetricsStore(debugPerformance);
375
+ // Wrapper to pass debugPerformance to external createMetricsStore.
376
+ // Also checks per-request flag set by ctx.debugPerformance() in middleware.
377
+ const getMetricsStore = () => {
378
+ const reqCtx = _getRequestContext();
379
+ const enabled = debugPerformance || !!reqCtx?._debugPerformance;
380
+ if (!enabled) return undefined;
381
+ if (!reqCtx) {
382
+ return createMetricsStore(true);
383
+ }
384
+ reqCtx._metricsStore ??= createMetricsStore(true);
385
+ return reqCtx._metricsStore;
386
+ };
362
387
 
363
388
  // Wrapper to pass defaults to error/notFound boundary finders
364
389
  const findNearestErrorBoundary = (entry: EntryData | null) =>
@@ -510,6 +535,7 @@ export function createRouter<TEnv = any>(
510
535
  trackHandler,
511
536
  findNearestErrorBoundary,
512
537
  findNearestNotFoundBoundary,
538
+ notFoundComponent: notFound,
513
539
  callOnError,
514
540
  };
515
541
 
@@ -544,6 +570,7 @@ export function createRouter<TEnv = any>(
544
570
  mergedRouteMap,
545
571
  nextMountIndex: () => mountIndex++,
546
572
  getPrecomputedByPrefix,
573
+ routerId,
547
574
  };
548
575
 
549
576
  function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
@@ -597,6 +624,8 @@ export function createRouter<TEnv = any>(
597
624
  params: Record<string, string>,
598
625
  buildVars?: Record<string, any>,
599
626
  isPassthroughRoute?: boolean,
627
+ buildEnv?: TEnv,
628
+ devMode?: boolean,
600
629
  ) {
601
630
  return _matchForPrerender(
602
631
  pathname,
@@ -604,6 +633,8 @@ export function createRouter<TEnv = any>(
604
633
  prerenderDeps,
605
634
  buildVars,
606
635
  isPassthroughRoute,
636
+ buildEnv,
637
+ devMode,
607
638
  );
608
639
  }
609
640
 
@@ -611,12 +642,16 @@ export function createRouter<TEnv = any>(
611
642
  handler: Function,
612
643
  handlerId: string,
613
644
  routeName?: string,
645
+ buildEnv?: TEnv,
646
+ devMode?: boolean,
614
647
  ) {
615
648
  return _renderStaticSegment<TEnv>(
616
649
  handler,
617
650
  handlerId,
618
651
  mergedRouteMap,
619
652
  routeName,
653
+ buildEnv,
654
+ devMode,
620
655
  );
621
656
  }
622
657
 
@@ -641,8 +676,15 @@ export function createRouter<TEnv = any>(
641
676
  const router: RSCRouterInternal<TEnv, {}> = {
642
677
  __brand: RSC_ROUTER_BRAND,
643
678
  id: routerId,
679
+ basename,
680
+
681
+ routes(patternsOrBuilder: UrlPatterns<TEnv> | UrlBuilder<TEnv>): any {
682
+ // Wrap builder functions in urls() automatically
683
+ const urlPatterns: UrlPatterns<TEnv> =
684
+ typeof patternsOrBuilder === "function"
685
+ ? (urls(patternsOrBuilder) as UrlPatterns<TEnv>)
686
+ : patternsOrBuilder;
644
687
 
645
- routes(urlPatterns: UrlPatterns<TEnv>): any {
646
688
  // Store reference for runtime manifest generation
647
689
  storedUrlPatterns = urlPatterns;
648
690
  const currentMountIndex = mountIndex++;
@@ -673,7 +715,7 @@ export function createRouter<TEnv = any>(
673
715
  errorBoundary: [],
674
716
  notFoundBoundary: [],
675
717
  layout: [],
676
- parallel: [],
718
+ parallel: {},
677
719
  intercept: [],
678
720
  loader: [],
679
721
  };
@@ -690,6 +732,10 @@ export function createRouter<TEnv = any>(
690
732
  counters: {},
691
733
  mountIndex: currentMountIndex,
692
734
  cacheProfiles: resolvedCacheProfiles,
735
+ // basename sets the initial URL prefix so all path() patterns
736
+ // are registered with the prefix (e.g. "/admin" + "/users" = "/admin/users").
737
+ // No namePrefix — route names stay unprefixed.
738
+ ...(basename ? { urlPrefix: basename } : {}),
693
739
  },
694
740
  () => {
695
741
  handlerResult = urlPatterns.handler() as AllUseItems[];
@@ -709,7 +755,7 @@ export function createRouter<TEnv = any>(
709
755
  if (entry.type === "route" && entry.isPrerender) {
710
756
  if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
711
757
  prerenderRouteKeys.add(name);
712
- if (entry.prerenderDef?.options?.passthrough === true) {
758
+ if (entry.isPassthrough === true) {
713
759
  if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
714
760
  passthroughRouteKeys.add(name);
715
761
  }
@@ -735,6 +781,7 @@ export function createRouter<TEnv = any>(
735
781
  trailingSlash: trailingSlashConfig,
736
782
  handler: urlPatterns.handler,
737
783
  mountIndex: currentMountIndex,
784
+ routerId,
738
785
  cacheProfiles: resolvedCacheProfiles,
739
786
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
740
787
  ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
@@ -754,6 +801,7 @@ export function createRouter<TEnv = any>(
754
801
  trailingSlash: trailingSlashConfig,
755
802
  handler: urlPatterns.handler,
756
803
  mountIndex: currentMountIndex,
804
+ routerId,
757
805
  cacheProfiles: resolvedCacheProfiles,
758
806
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
759
807
  ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
@@ -797,6 +845,7 @@ export function createRouter<TEnv = any>(
797
845
  trailingSlash: trailingSlashConfig,
798
846
  handler: urlPatterns.handler,
799
847
  mountIndex: mountIndex++,
848
+ routerId,
800
849
  // Lazy evaluation fields
801
850
  lazy: true,
802
851
  lazyPatterns: lazyInclude.patterns,
@@ -835,8 +884,18 @@ export function createRouter<TEnv = any>(
835
884
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
836
885
  middleware?: MiddlewareFn<TEnv>,
837
886
  ): any {
838
- // Global middleware - no mount prefix
839
- addMiddleware(patternOrMiddleware, middleware, null);
887
+ // Auto-prefix pattern with basename so router-level middleware
888
+ // patterns are router-relative (e.g. "/users/*" matches "/app/users/*").
889
+ if (basename && typeof patternOrMiddleware === "string") {
890
+ const pattern = patternOrMiddleware;
891
+ const prefixed =
892
+ pattern === "/*" || pattern === "*"
893
+ ? `${basename}/*`
894
+ : `${basename}${pattern}`;
895
+ addMiddleware(prefixed, middleware, null);
896
+ } else {
897
+ addMiddleware(patternOrMiddleware, middleware, null);
898
+ }
840
899
  return router;
841
900
  },
842
901
 
@@ -869,12 +928,16 @@ export function createRouter<TEnv = any>(
869
928
  // Expose resolved cache profiles for per-request resolution
870
929
  cacheProfiles: resolvedCacheProfiles,
871
930
 
872
- // Expose prefetch cache control for RSC handler
931
+ // Expose prefetch cache settings
873
932
  prefetchCacheControl,
933
+ prefetchCacheTTL,
874
934
 
875
935
  // Expose warmup enabled flag for handler and client
876
936
  warmupEnabled,
877
937
 
938
+ // Expose router-wide performance debugging for request-level metrics setup
939
+ debugPerformance,
940
+
878
941
  // Expose debug manifest flag for handler
879
942
  allowDebugManifest: allowDebugManifestOption,
880
943
 
@@ -933,6 +996,9 @@ export function createRouter<TEnv = any>(
933
996
  // Expose source file for per-router type generation
934
997
  __sourceFile,
935
998
 
999
+ // Expose basename for runtime manifest generation
1000
+ __basename: basename,
1001
+
936
1002
  // RSC request handler (lazily created on first call)
937
1003
  fetch: (() => {
938
1004
  // Handler is created on first call and reused
@@ -966,6 +1032,10 @@ export function createRouter<TEnv = any>(
966
1032
  };
967
1033
  })(),
968
1034
 
1035
+ // Low-level route matching for request classification
1036
+ findMatch: (pathname: string, metricsStore?: any) =>
1037
+ findMatch(pathname, metricsStore),
1038
+
969
1039
  // Debug utility for manifest inspection
970
1040
  debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
971
1041
  };
@@ -974,7 +1044,9 @@ export function createRouter<TEnv = any>(
974
1044
  RouterRegistry.set(routerId, router);
975
1045
 
976
1046
  // If urls option was provided, auto-register them
977
- if (urlsOption) {
1047
+ if (typeof urlsOption === "function") {
1048
+ return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1049
+ } else if (urlsOption) {
978
1050
  return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
979
1051
  }
980
1052