From f0777f9bfd22e7facea8311f4ef79b41ce00e42b Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Wed, 7 Oct 2026 05:54:23 -0500 Subject: [PATCH 1/2] fix(react-router): a lazy route links the page a barrel module forwards A data router's `lazy: () => import('@/pages/invocation_page')` loads the folder's `index.ts`. When that file only forwards the page, with `export * from './invocation_page'`, `export { default } from './page'` or `export { Page as Component } from './page'`, the route linked nothing: the module was read for an export of its own and none was found. The lookup now follows the module's re-exports a few hops to the file that declares the page, the way JavaScript resolves them (`export *` never forwards the default, a name two `export *` modules both forward is exported by neither), and reads a module's own `export { Page as Component }` clause. `React.lazy` values in a route file, which go through the same lookup, follow such barrels too. On luci-go's milo/ui, 51 of 91 failed lazy route refs now link; the rest load a file under `src/build/` (not indexed), export only a `loader` or a `handle`, or pick a named export in an async loader. Co-Authored-By: Claude Opus 5.5 --- __tests__/react-router-lazy-barrel.test.ts | 190 +++++++++++++++++++++ src/resolution/frameworks/react.ts | 65 ++++++- 2 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 __tests__/react-router-lazy-barrel.test.ts diff --git a/__tests__/react-router-lazy-barrel.test.ts b/__tests__/react-router-lazy-barrel.test.ts new file mode 100644 index 000000000..60cc2703e --- /dev/null +++ b/__tests__/react-router-lazy-barrel.test.ts @@ -0,0 +1,190 @@ +/** + * A lazily loaded route module that is a barrel (luci-go's milo/ui): + * `lazy: () => import('@/test_verdict/pages/invocation_page')` loads + * `invocation_page/index.ts`, whose `export * from './invocation_page'` + * forwards the `Component` that `invocation_page.tsx` declares. The route + * renders that component; before, the barrel was read for an export of its + * own, found none, and the route linked nothing. (The fixture's folder is + * `builds/`: a `build/` folder is skipped as build output.) + */ +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; + +let root = ''; +let cg: CodeGraph; + +beforeAll(async () => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rr-lazy-barrel-')); + const files: Record = { + 'package.json': JSON.stringify({ name: 'ui', private: true, dependencies: { react: '^18', 'react-router': '^7' } }), + 'tsconfig.json': JSON.stringify({ compilerOptions: { baseUrl: '.', paths: { '@/*': ['./src/*'] } } }), + 'src/router.tsx': `import { lazy } from 'react'; +import { createBrowserRouter } from 'react-router'; + +const Settings = lazy(() => import('@/settings')); + +export const router = createBrowserRouter([ + { + path: '/builds/:id', + lazy: () => import('@/builds/build_page'), + children: [{ path: 'overview', lazy: () => import('@/builds/build_page/overview_tab') }], + }, + { path: '/builders', lazy: () => import('@/builds/pages') }, + { path: '/fleet', lazy: () => import('@/fleet/home_page') }, + { path: '/login', lazy: () => import('@/auth/login_page') }, + { path: '/graph', lazy: () => import('@/chronicle/graph_view') }, + { path: '/settings', element: }, + { path: '/redirect', lazy: () => import('@/core/redirection_loader') }, + { path: '/clash', lazy: () => import('@/clash') }, + { path: '/loop', lazy: () => import('@/loop/a') }, + { path: '/no-default', lazy: () => import('@/legacy') }, +]); +`, + // `export *` forwards the `Component` its module declares. + 'src/builds/build_page/index.ts': `export * from './build_page'; +`, + 'src/builds/build_page/build_page.tsx': `export const BuildPage = () => null; + +export function Component() { + return ; +} +`, + 'src/builds/build_page/overview_tab/index.ts': `export * from './overview_tab'; +`, + 'src/builds/build_page/overview_tab/overview_tab.tsx': `export function Component() { + return null; +} +`, + // A barrel of barrels. + 'src/builds/pages/index.ts': `export * from './builder_list_page'; +`, + 'src/builds/pages/builder_list_page/index.ts': `export * from './builder_list_page'; +`, + 'src/builds/pages/builder_list_page/builder_list_page.tsx': `export function BuilderListPage() { + return null; +} + +export const Component = BuilderListPage; +`, + // A named re-export, renamed to the name React Router reads. + 'src/fleet/home_page/index.ts': `export { HomePage as Component } from './home_page'; +`, + 'src/fleet/home_page/home_page.tsx': `export function HomePage() { + return null; +} +`, + // The default export, forwarded. + 'src/auth/login_page/index.ts': `export { default } from './login_page'; +`, + 'src/auth/login_page/login_page.tsx': `export default function LoginPage() { + return null; +} +`, + // An export clause in the module itself. + 'src/chronicle/graph_view.tsx': `function GraphView() { + return null; +} + +export { GraphView as Component }; +`, + // `React.lazy` reads the default export, through a barrel too. + 'src/settings/index.ts': `export { default } from './settings_page'; +`, + 'src/settings/settings_page.tsx': `export default function SettingsPage() { + return null; +} +`, + // A module that only redirects renders nothing. + 'src/core/redirection_loader.ts': `export function redirectionLoader() { + return null; +} + +export const loader = redirectionLoader; +`, + // Two modules forward a `Component` through `export *`: JavaScript exports neither. + 'src/clash/index.ts': `export * from './first'; +export * from './second'; +`, + 'src/clash/first.tsx': `export function Component() { + return null; +} +`, + 'src/clash/second.tsx': `export function Component() { + return null; +} +`, + // Barrels that forward each other. + 'src/loop/a.ts': `export * from './b'; +`, + 'src/loop/b.ts': `export * from './a'; +`, + // `export *` forwards every name but the default. + 'src/legacy/index.ts': `export * from './legacy_page'; +`, + 'src/legacy/legacy_page.tsx': `export default function LegacyPage() { + return null; +} +`, + }; + for (const [rel, content] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); + fs.writeFileSync(path.join(root, rel), content); + } + cg = await CodeGraph.init(root, { index: true }); +}, 60_000); + +afterAll(() => { + cg?.close(); + if (root) fs.rmSync(root, { recursive: true, force: true }); +}); + +/** What a route's `references` edges reach, as `name@file`: what it renders, or the layouts around it. */ +const bindings = (routeName: string, layout: boolean): string[] => { + const route = cg.getNodesByKind('route').find((r) => r.name === routeName); + if (!route) return [`no route ${routeName}`]; + return cg.getOutgoingEdges(route.id) + .filter((e) => e.kind === 'references' && Boolean((e.metadata as Record | undefined)?.layout) === layout) + .map((e) => cg.getNode(e.target)!) + .map((n) => `${n.name}@${n.filePath}`); +}; +const renders = (routeName: string): string[] => bindings(routeName, false); + +describe('a lazy route module that is a barrel', () => { + it('renders the Component an `export *` barrel forwards', () => { + expect(renders('/builds/:id')).toEqual(['Component@src/builds/build_page/build_page.tsx']); + expect(renders('/builds/:id/overview')).toEqual(['Component@src/builds/build_page/overview_tab/overview_tab.tsx']); + }); + + it('follows a barrel that forwards another barrel', () => { + expect(renders('/builders')).toEqual(['Component@src/builds/pages/builder_list_page/builder_list_page.tsx']); + }); + + it('renders inside the layout a barrel forwards', () => { + expect(bindings('/builds/:id/overview', true)).toEqual(['Component@src/builds/build_page/build_page.tsx']); + }); + + it('follows a named re-export to what it renames', () => { + expect(renders('/fleet')).toEqual(['HomePage@src/fleet/home_page/home_page.tsx']); + expect(renders('/login')).toEqual(['LoginPage@src/auth/login_page/login_page.tsx']); + }); + + it('reads an export clause in the module itself', () => { + expect(renders('/graph')).toEqual(['GraphView@src/chronicle/graph_view.tsx']); + }); + + it('follows the barrel a React.lazy value loads', () => { + expect(renders('/settings')).toEqual(['SettingsPage@src/settings/settings_page.tsx']); + }); + + it('links nothing a barrel does not forward', () => { + // A loader-only module, a name two `export *` modules both forward, a + // cycle, and a default export behind `export *`. + expect(renders('/redirect')).toEqual([]); + expect(renders('/clash')).toEqual([]); + expect(renders('/loop')).toEqual([]); + expect(renders('/no-default')).toEqual([]); + }); +}); diff --git a/src/resolution/frameworks/react.ts b/src/resolution/frameworks/react.ts index 72d05c91d..d114c2dbf 100644 --- a/src/resolution/frameworks/react.ts +++ b/src/resolution/frameworks/react.ts @@ -9,6 +9,7 @@ import { Language, Node } from '../../types'; import { FrameworkExtractionResult, FrameworkResolver, UnresolvedRef, ResolvedRef, ResolutionContext } from '../types'; import { dependsOn } from './package-deps'; import { resolveImportPath } from '../import-resolver'; +import { extractLocalExportAliases } from '../alias-binding'; import { stripCommentsForRegex } from '../strip-comments'; import { makeLineAt } from '../synth-utils'; import { isTestPath } from '../../search/query-utils'; @@ -402,13 +403,63 @@ function readObjectPath(text: string, at: number, keys: string[]): string | null function lazyRouteComponent(spec: string, fromFile: string, context: ResolutionContext): string | null { const file = resolveImportPath(spec, fromFile, 'typescript', context); if (!file) return null; - const source = context.readFile(file) ?? ''; - const named = /\bexport\s+default\s+(?:async\s+)?(?:function\s*\*?\s*|class\s+)?([A-Za-z_$][\w$]*)/.exec(source)?.[1] ?? - (/\bexport\s+(?:const|function|class)\s+Component\b/.test(source) ? 'Component' : null); - if (!named) return null; - const node = context.getNodesInFile(file).find((n) => n.name === named && - (n.kind === 'function' || n.kind === 'component' || n.kind === 'class' || n.kind === 'constant' || n.kind === 'variable')); - return node?.id ?? null; + return exportedComponent(file, 'default', context) ?? exportedComponent(file, 'Component', context); +} + +/** What a module can export as a component: a declaration, or a value holding one. */ +const EXPORTED_COMPONENT_KINDS = new Set(['function', 'component', 'class', 'constant', 'variable']); + +/** A barrel can forward another barrel; a few hops settle it. */ +const MAX_REEXPORT_HOPS = 4; + +/** + * The component `file` exports as `name`: one it declares — `export default + * Page`, `export function Component`, `export { Page as Component }` — or one + * it forwards from the module that declares it, as a barrel `index.ts` does: + * `export { default } from './page'`, `export { Page as Component } from + * './page'`, or `export * from './page'`, which forwards every name but the + * default. As in JavaScript, a name the file exports itself hides one an + * `export *` would forward, and a name two `export *` modules both forward is + * exported by neither. + */ +function exportedComponent(file: string, name: string, context: ResolutionContext, visited: ReadonlySet = new Set()): string | null { + if (visited.has(file) || visited.size > MAX_REEXPORT_HOPS) return null; + const local = ownExport(stripCommentsForRegex(context.readFile(file) ?? '', 'typescript'), name); + if (local) return context.getNodesInFile(file).find((n) => n.name === local && EXPORTED_COMPONENT_KINDS.has(n.kind))?.id ?? null; + const language = scriptLanguage(file); + const reExports = context.getReExports?.(file, language) ?? []; + const through = new Set(visited).add(file); + const forwarded = (source: string, exported: string): string | null => { + const target = resolveImportPath(source, file, language, context); + return target ? exportedComponent(target, exported, context, through) : null; + }; + for (const re of reExports) { + if (re.kind === 'named' && re.exportedName === name) return forwarded(re.source, re.originalName); + } + if (name === 'default') return null; + let found: string | null = null; + for (const re of reExports) { + if (re.kind !== 'wildcard') continue; + const id = forwarded(re.source, name); + if (id && found && id !== found) return null; + found ??= id; + } + return found; +} + +/** + * The local name a module's own `export` gives `name`: `export default Page` + * (or `function Page`, `class Page`), `export function Component`, `export + * const Component`, `export { Page as Component }`. Null when it has none. + */ +function ownExport(source: string, name: string): string | null { + if (name === 'default') { + const declared = /\bexport\s+default\s+(?:async\s+)?(?:function\s*\*?\s*|class\s+)?([A-Za-z_$][\w$]*)/.exec(source)?.[1]; + if (declared) return declared; + } else if (new RegExp(String.raw`\bexport\s+(?:async\s+)?(?:const|let|var|function|class)\s+${name.replace(/\$/g, '\\$')}(?![\w$])`).test(source)) { + return name; + } + return extractLocalExportAliases(source).find((a) => a.exportedName === name)?.localName ?? null; } /** From 348a3b26a3b74691e5914213dddf0555235e6d0e Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Wed, 7 Oct 2026 08:03:13 -0500 Subject: [PATCH 2/2] test(react-router): sync links a page that appears behind its barrel Pins the sync side of the barrel fix: a page added behind its folder's index file after the index, and an edit that gives the page the `Component` its barrel forwards, both link on `sync` the way a fresh index links them. Both fail with sync's failed-reference retry stubbed out. Adds the CHANGELOG entry and the validation note in the framework coverage doc. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + __tests__/react-router-lazy-barrel.test.ts | 85 +++++++++++++++++++++- docs/design/framework-coverage.md | 2 +- 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08226636c..b623c29c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - React Router routes kept in a table of their own are now indexed: the ASP.NET Core React template's `AppRoutes` array that `App.js` maps into ``, a `routes` array imported into `useRoutes(routes)` or `createBrowserRouter(routes)` or returned by a function as in `useRoutes(routes(isLoggedIn))`, route objects kept one per file and listed in `createBrowserRouter([MainRoutes, LoginRoutes])`, and routes written in place in `useRoutes([…])`. Before, route objects were read only from a file that itself creates a data router, so these apps had no routes, and their `navigate('/login')` calls and `` links led nowhere. Each route links to the page its `element` renders, past a guard like `` or ``, and through `lazy(() => import(…))` to the page it loads rather than a same-named page elsewhere in the repository. An `index: true` route is the page at its parent's address, and a parent route around others counts as their layout. A `{ path, element }` list that nothing hands to the router, such as a menu, still makes no routes. Re-index React projects after upgrading. - In Go, a call or type written through an import whose path ends in a version, or in something other than the package's name, is now known to go through that import: `yaml.Unmarshal(…)` after `import "go.yaml.in/yaml/v3"` or `"gopkg.in/yaml.v3"`, `sqlite3.Error` after `import "github.com/mattn/go-sqlite3"`, `klog.V(2)` after `import "k8s.io/klog/v2"`. Before, only the last part of the path named such an import (`v3`, `yaml.v3`, `go-sqlite3`), so the name was matched on its own and could link to any project function, method or type that shared it: kubernetes' `klog.V(…)` calls were linked to a logging wrapper's `V` method, and etcd's `semver.Version` parameters to an unrelated `Version` function. A name from another module now links to nothing, and one through a package of your own project, like `kit.New()` after `import "example.com/kit/v2"`, links to that package's symbol. A comment in an import block is also no longer taken for the name of the import after it. Re-index Go projects after upgrading. Thanks @danusha2345 for the comment fix. (#2374) - React Router routes written as JSX now read `` the way route objects do: inside `}>`, `} />` is the page at `/`, and `Layout` counts as the layout of every route nested in it, as does a guard with no path of its own like `}>`. Before, the index route was never read, so `/` led to the layout itself and a `` holding only an index route led to nothing. A route's `element` written on lines of its own, the way Prettier formats a long one, or wrapped in `}>`, now links to the page it shows rather than to nothing or to the fallback, and a path written in braces, like `path={"agents"}`, is read like any other. Re-index React projects after upgrading. +- A React Router route that lazily loads a folder, like `lazy: () => import('@/pages/invocation_page')`, now links to the page the folder's `index.ts` passes on, whether it writes `export * from './invocation_page'`, `export { default } from './page'` or `export { Page as Component } from './page'`, even through another `index.ts`. A route module that names its page with `export { Page as Component }` links to it as well, and so does a page loaded with `lazy(() => import(…))` through such an `index.ts`. Before, these routes linked to nothing, so their pages had no route leading to them. Re-index React projects after upgrading. - In JavaScript and TypeScript, an import written `import { default as AppRoot } from './routes/app/root'`, as in bulletproof-react's router, now counts as the module's default import, the same as `import AppRoot from './routes/app/root'`. Before, it was read as an import of an export named `default`, which no module has, so a call, a route or a JSX attribute that used `AppRoot` was matched by its name alone: it could link to another file's `AppRoot`, or to nothing when the default export has a name of its own. Svelte, Vue and Astro script blocks are read the same way. ## [1.6.2] - 2026-10-03 diff --git a/__tests__/react-router-lazy-barrel.test.ts b/__tests__/react-router-lazy-barrel.test.ts index 60cc2703e..9bc5d5705 100644 --- a/__tests__/react-router-lazy-barrel.test.ts +++ b/__tests__/react-router-lazy-barrel.test.ts @@ -7,7 +7,7 @@ * own, found none, and the route linked nothing. (The fixture's folder is * `builds/`: a `build/` folder is skipped as build output.) */ -import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import { describe, it, expect, afterAll, afterEach, beforeAll } from 'vitest'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -188,3 +188,86 @@ describe('a lazy route module that is a barrel', () => { expect(renders('/no-default')).toEqual([]); }); }); + +describe('sync, for a lazy route module that is a barrel', () => { + type Files = Record; + const ROUTER: Files = { + 'package.json': JSON.stringify({ name: 'ui', private: true, dependencies: { react: '^18', 'react-router': '^7' } }), + 'tsconfig.json': JSON.stringify({ compilerOptions: { baseUrl: '.', paths: { '@/*': ['./src/*'] } } }), + 'src/router.tsx': `import { createBrowserRouter } from 'react-router'; + +export const router = createBrowserRouter([ + { path: '/reports', lazy: () => import('@/reports/report_list') }, + { path: '/reports/:id', lazy: () => import('@/reports/report_page') }, +]); +`, + 'src/reports/report_list/index.ts': `export { ReportList as Component } from './report_list'; +`, + 'src/reports/report_page/index.ts': `export * from './report_page'; +`, + }; + const PAGES: Files = { + 'src/reports/report_list/report_list.tsx': `export function ReportList() { + return null; +} +`, + 'src/reports/report_page/report_page.tsx': `export function Component() { + return null; +} +`, + }; + const LINKED = { + '/reports': ['ReportList@src/reports/report_list/report_list.tsx'], + '/reports/:id': ['Component@src/reports/report_page/report_page.tsx'], + }; + + let dirs: string[] = []; + let graphs: CodeGraph[] = []; + afterEach(() => { + for (const graph of graphs) graph.close(); + for (const dir of dirs) fs.rmSync(dir, { recursive: true, force: true }); + graphs = []; + dirs = []; + }); + + const write = (dir: string, files: Files): void => { + for (const [rel, content] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true }); + fs.writeFileSync(path.join(dir, rel), content); + } + }; + /** A project of `files`, indexed from scratch in a folder of its own. */ + const indexed = async (files: Files): Promise<{ dir: string; graph: CodeGraph }> => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rr-lazy-barrel-sync-')); + dirs.push(dir); + write(dir, files); + const graph = await CodeGraph.init(dir, { index: true }); + graphs.push(graph); + return { dir, graph }; + }; + /** Each route's `references` edges, as `name@file`. */ + const routeLinks = (graph: CodeGraph): Record => Object.fromEntries( + graph.getNodesByKind('route').map((route) => [route.name, graph.getOutgoingEdges(route.id) + .filter((e) => e.kind === 'references') + .map((e) => graph.getNode(e.target)!) + .map((n) => `${n.name}@${n.filePath}`) + .sort()]) + ); + + it('links a page that appears behind its barrel later', async () => { + const { dir, graph } = await indexed(ROUTER); + expect(routeLinks(graph)).toEqual({ '/reports': [], '/reports/:id': [] }); + write(dir, PAGES); + expect((await graph.sync()).filesAdded).toBe(2); + expect(routeLinks(graph)).toEqual(LINKED); + expect(routeLinks((await indexed({ ...ROUTER, ...PAGES })).graph)).toEqual(LINKED); + }, 60_000); + + it('links a page once an edit gives it the Component its barrel forwards', async () => { + const { dir, graph } = await indexed({ ...ROUTER, ...PAGES, 'src/reports/report_page/report_page.tsx': 'export const placeholder = 1;\n' }); + expect(routeLinks(graph)['/reports/:id']).toEqual([]); + write(dir, { 'src/reports/report_page/report_page.tsx': PAGES['src/reports/report_page/report_page.tsx']! }); + expect((await graph.sync()).filesModified).toBe(1); + expect(routeLinks(graph)).toEqual(LINKED); + }, 60_000); +}); diff --git a/docs/design/framework-coverage.md b/docs/design/framework-coverage.md index 1ac0eb989..6162ec447 100644 --- a/docs/design/framework-coverage.md +++ b/docs/design/framework-coverage.md @@ -38,7 +38,7 @@ guessed. |---|---|---|---|---| | Expo Router | `frameworks/expo-router.ts` | `expo-router-synthesizer.ts` | `expo-router.test.ts` | — | | Next.js | `frameworks/nextjs.ts` | `next-router-synthesizer.ts` | `nextjs.test.ts` | next-saas-starter | -| React Router | `frameworks/react-router.ts` | `react-router-synthesizer.ts` | `react-router.test.ts` | proshop (44 edges), proshop-v2 (28), react-redux-realworld (22), react-boilerplate (`styled(Link)`), takenote (v5 ``), bulletproof-react (nested `children`, `lazy` routes, `paths.x.path` constants through an app's own tsconfig alias: 0 → 9 named, linked routes), and its `paths.x.getHref(id)` links and `navigate(paths.x.getHref())` calls (0 → 11 navigates). Route tables another file hands the router (`tableRoutes` in `frameworks/react.ts`, trap 13): the ASP.NET Core React template's `AppRoutes.map(… )` on jasontaylordev/CleanArchitecture's `ClientApp-React` (0 → 6 routes, 11 navigates), replaysMike/Binner (0 → 46, 87), GavinLonDigital/RankingApp, GarrettHays/PaydirtPickem, charlessolar/eShopOnContainersDDD; `useRoutes(routes)` from another file and `[...sessionRoutes]` on uilibrary/matx-react (0 → 7, 16); codedthemes' `createBrowserRouter([MainRoutes, LoginRoutes])` on berry and mantis (9 and 8 routes, no navigation they can resolve); a table `.map`ped inside `` on RADeveloping/chickadeeinvest; `useRoutes([...])` in place on minimal-ui-kit/material-kit-react (0 → 7, bound through `lazy(() => import(…))` to each page). JSX index routes and layouts (trap 14): proshop-v2's `createRoutesFromElements` (`/` is HomeScreen inside App, not App; 30 layout edges, the `path=''` guards included; Screens 30 → 74 transitions, the guards' redirects drawn from the screens they guard), chickadeeinvest's table inside `}>` (its 4 routes get the layout), React Router 6.29's own examples and tests, refine's 281 examples (one `/` per app, 191 layout edges, 21 new navigations), crwn-clothing, the vanlife course app, volun-mern (105 layout edges, six levels deep), spotify/vispana, react_persist_login, chushi (`path={"agents"}`: 2 → 6 routes), hathor-explorer (`path=""` at the root), cboard, react-router-auth-v6: 578 of 581 new layout edges name a component of an enclosing `` (vanlife's 3 inherit a default-import resolution bug that already misbinds its pages), 27 of 27 new navigations precise, no navigation lost, 16 control repos byte-identical | +| React Router | `frameworks/react-router.ts` | `react-router-synthesizer.ts` | `react-router.test.ts` | proshop (44 edges), proshop-v2 (28), react-redux-realworld (22), react-boilerplate (`styled(Link)`), takenote (v5 ``), bulletproof-react (nested `children`, `lazy` routes, `paths.x.path` constants through an app's own tsconfig alias: 0 → 9 named, linked routes), and its `paths.x.getHref(id)` links and `navigate(paths.x.getHref())` calls (0 → 11 navigates). Route tables another file hands the router (`tableRoutes` in `frameworks/react.ts`, trap 13): the ASP.NET Core React template's `AppRoutes.map(… )` on jasontaylordev/CleanArchitecture's `ClientApp-React` (0 → 6 routes, 11 navigates), replaysMike/Binner (0 → 46, 87), GavinLonDigital/RankingApp, GarrettHays/PaydirtPickem, charlessolar/eShopOnContainersDDD; `useRoutes(routes)` from another file and `[...sessionRoutes]` on uilibrary/matx-react (0 → 7, 16); codedthemes' `createBrowserRouter([MainRoutes, LoginRoutes])` on berry and mantis (9 and 8 routes, no navigation they can resolve); a table `.map`ped inside `` on RADeveloping/chickadeeinvest; `useRoutes([...])` in place on minimal-ui-kit/material-kit-react (0 → 7, bound through `lazy(() => import(…))` to each page). JSX index routes and layouts (trap 14): proshop-v2's `createRoutesFromElements` (`/` is HomeScreen inside App, not App; 30 layout edges, the `path=''` guards included; Screens 30 → 74 transitions, the guards' redirects drawn from the screens they guard), chickadeeinvest's table inside `}>` (its 4 routes get the layout), React Router 6.29's own examples and tests, refine's 281 examples (one `/` per app, 191 layout edges, 21 new navigations), crwn-clothing, the vanlife course app, volun-mern (105 layout edges, six levels deep), spotify/vispana, react_persist_login, chushi (`path={"agents"}`: 2 → 6 routes), hathor-explorer (`path=""` at the root), cboard, react-router-auth-v6: 578 of 581 new layout edges name a component of an enclosing `` (vanlife's 3 inherit a default-import resolution bug that already misbinds its pages), 27 of 27 new navigations precise, no navigation lost, 16 control repos byte-identical. Lazy route modules that are barrels (`exportedComponent` in `frameworks/react.ts`): luci-go's milo/ui (`export * from './x'` index files and `export { X as Component }` clauses: 0 → 51 lazy routes linked; the other 40 load a file under `src/build/`, which the default ignore skips, export only a `loader` or a `handle`, or pick a named export in an async `lazy`), Medusa's admin dashboard (all 193 route-map modules resolve through its `export { X as Component } from` barrels, but its routes are not extracted: `getRouteMap()` returns the table), four more apps (9 routes), 16 control repos byte-identical | | TanStack Router | `frameworks/tanstack-router.ts` | `tanstack-router-synthesizer.ts` | `tanstack-router.test.ts` | TanStack examples, fastapi-template frontend | | Vue Router / Nuxt | `frameworks/vue-router.ts` (Nuxt file routes: `nuxtResolver` in `frameworks/vue.ts`) | `vue-router-synthesizer.ts` | `vue-router.test.ts` | vue-realworld (23 edges); vue-element-admin (62 routes), vue-admin-template (14), vben (192), halo console (34) — named tables, module files, `children` + layouts; Nuxt: mealie, elk, nuxt/movies | | SvelteKit | `frameworks/sveltekit-router.ts` | `sveltekit-synthesizer.ts` | `sveltekit-router.test.ts`, `sveltekit-route-names.test.ts` | sveltekit-realworld (31 edges); shadcn-svelte and skeleton (`(group)` layouts: 13 and 23 edges), svelte.dev (74), kit's test apps (47) |