@rangojs/router 0.0.0-experimental.41 → 0.0.0-experimental.42

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.
@@ -1745,7 +1745,7 @@ import { resolve } from "node:path";
1745
1745
  // package.json
1746
1746
  var package_default = {
1747
1747
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.41",
1748
+ version: "0.0.0-experimental.42",
1749
1749
  description: "Django-inspired RSC router with composable URL patterns",
1750
1750
  keywords: [
1751
1751
  "react",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.41",
3
+ "version": "0.0.0-experimental.42",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -485,8 +485,16 @@ export function createNavigationBridge(
485
485
  onUpdate(popstateUpdate);
486
486
  }
487
487
 
488
- // Restore scroll position for back/forward navigation
489
- handleNavigationEnd({ restore: true, isStreaming });
488
+ // Restore scroll position for back/forward navigation.
489
+ // Defer to next frame so React has committed the cached content to
490
+ // the DOM before we measure scrollHeight and restore scroll position.
491
+ const defer =
492
+ typeof requestAnimationFrame === "function"
493
+ ? requestAnimationFrame
494
+ : (fn: () => void) => setTimeout(fn, 0);
495
+ defer(() => {
496
+ handleNavigationEnd({ restore: true, isStreaming });
497
+ });
490
498
 
491
499
  // SWR: If stale, trigger background revalidation
492
500
  if (isStale) {
@@ -288,7 +288,11 @@ export function restoreScrollPosition(options?: {
288
288
  // Not streaming — scroll after React commits and browser paints.
289
289
  // startTransition defers the DOM commit, so scrolling synchronously
290
290
  // would be overwritten when React replaces the content.
291
- requestAnimationFrame(() => {
291
+ const defer =
292
+ typeof requestAnimationFrame === "function"
293
+ ? requestAnimationFrame
294
+ : (fn: () => void) => setTimeout(fn, 0);
295
+ defer(() => {
292
296
  window.scrollTo(0, savedY);
293
297
  debugLog("[Scroll] Restored position:", savedY, "for key:", key);
294
298
  });
@@ -199,7 +199,13 @@ export function registerRouterManifestLoader(
199
199
  }
200
200
 
201
201
  export async function ensureRouterManifest(routerId: string): Promise<void> {
202
- if (perRouterManifestMap.has(routerId)) return;
202
+ // Check both manifest AND trie. The virtual module's setRouterManifest()
203
+ // pre-sets the manifest at startup, but the per-router trie is only
204
+ // available from the lazy loader. Without this, the lazy loader never
205
+ // runs and findMatch falls back to the global merged trie — which
206
+ // contains routes from ALL routers and breaks multi-router setups.
207
+ if (perRouterManifestMap.has(routerId) && perRouterTrieMap.has(routerId))
208
+ return;
203
209
  const loader = routerManifestLoaders.get(routerId);
204
210
  if (loader) {
205
211
  const mod = await loader();
@@ -52,8 +52,10 @@ export function createFindMatch<TEnv = any>(
52
52
  : undefined;
53
53
 
54
54
  // Phase 1: Try trie match (O(path_length))
55
- // Prefer per-router trie (isolated) over global trie (merged).
56
- const routeTrie = getRouterTrie(deps.routerId) ?? getRouteTrie();
55
+ // Only use the per-router trie. The global trie merges routes from ALL
56
+ // routers and must not be used — in multi-router setups (host routing)
57
+ // overlapping paths like "/" would match the wrong app's route.
58
+ const routeTrie = getRouterTrie(deps.routerId);
57
59
  if (routeTrie) {
58
60
  const trieStart = performance.now();
59
61
  const trieResult = tryTrieMatch(routeTrie, pathname);
@@ -14,6 +14,7 @@ export interface LazyEvalDeps<TEnv = any> {
14
14
  mergedRouteMap: Record<string, string>;
15
15
  nextMountIndex: () => number;
16
16
  getPrecomputedByPrefix: () => Map<string, Record<string, string>> | null;
17
+ routerId?: string;
17
18
  }
18
19
 
19
20
  // Detect lazy includes in handler result and create placeholder entries
@@ -200,6 +201,7 @@ export function evaluateLazyEntry<TEnv = any>(
200
201
  trailingSlash: entry.trailingSlash,
201
202
  handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
202
203
  mountIndex: deps.nextMountIndex(),
204
+ routerId: deps.routerId,
203
205
  // Lazy evaluation fields
204
206
  lazy: true,
205
207
  lazyPatterns: lazyInclude.patterns,
@@ -65,7 +65,9 @@ export async function loadManifest(
65
65
  const mountIndex = entry.mountIndex;
66
66
 
67
67
  // Check module-level cache (persists across requests within same isolate)
68
- const cacheKey = `${VERSION}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
68
+ // Include routerId so multi-router setups (host routing) don't share cached
69
+ // EntryData across routers with overlapping mountIndex + routeKey combinations.
70
+ const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
69
71
  const cached = manifestModuleCache.get(cacheKey);
70
72
  if (cached) {
71
73
  const cacheStart = performance.now();
package/src/router.ts CHANGED
@@ -560,6 +560,7 @@ export function createRouter<TEnv = any>(
560
560
  mergedRouteMap,
561
561
  nextMountIndex: () => mountIndex++,
562
562
  getPrecomputedByPrefix,
563
+ routerId,
563
564
  };
564
565
 
565
566
  function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
@@ -751,6 +752,7 @@ export function createRouter<TEnv = any>(
751
752
  trailingSlash: trailingSlashConfig,
752
753
  handler: urlPatterns.handler,
753
754
  mountIndex: currentMountIndex,
755
+ routerId,
754
756
  cacheProfiles: resolvedCacheProfiles,
755
757
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
756
758
  ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
@@ -770,6 +772,7 @@ export function createRouter<TEnv = any>(
770
772
  trailingSlash: trailingSlashConfig,
771
773
  handler: urlPatterns.handler,
772
774
  mountIndex: currentMountIndex,
775
+ routerId,
773
776
  cacheProfiles: resolvedCacheProfiles,
774
777
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
775
778
  ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
@@ -813,6 +816,7 @@ export function createRouter<TEnv = any>(
813
816
  trailingSlash: trailingSlashConfig,
814
817
  handler: urlPatterns.handler,
815
818
  mountIndex: mountIndex++,
819
+ routerId,
816
820
  // Lazy evaluation fields
817
821
  lazy: true,
818
822
  lazyPatterns: lazyInclude.patterns,
@@ -55,6 +55,13 @@ export interface RouteEntry<TEnv = any> {
55
55
  | Promise<() => Array<AllUseItems>>;
56
56
  mountIndex: number;
57
57
 
58
+ /**
59
+ * Router ID that owns this entry. Used to namespace the manifest cache
60
+ * so multi-router setups (host routing) don't share cached EntryData
61
+ * across routers with overlapping mountIndex + routeKey combinations.
62
+ */
63
+ routerId?: string;
64
+
58
65
  /**
59
66
  * Route keys in this entry that have pre-render handlers.
60
67
  * Used by the non-trie match path to set the `pr` flag.