Skip to content
Back to skills

Route Discovery System

ASecurity

Filesystem auto-discovery of App Router pages into navigable index pages. Use when creating an index/listing page, adding route directories, converting hard-coded route configs to auto-discovery, or adding a route-listing display variant.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 3, 2026
developmentgoshellreactnode

Works with

  • cli

Security analysis

A100/100

Scanned October 3, 2026

npx -y skills add armanisadeghi/ai-matrx --skill route-discovery-system --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Route Discovery System?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Route Discovery System
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/armanisadeghi-route-discovery-system/badge)](https://www.skillsdirectory.com/skills/armanisadeghi-route-discovery-system)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: route-discovery-system
description: "Filesystem auto-discovery of App Router pages into navigable index pages. Use when creating an index/listing page, adding route directories, converting hard-coded route configs to auto-discovery, or adding a route-listing display variant."
---

# Route Discovery System

Filesystem-based route auto-discovery that replaces hard-coded `config.ts` route arrays. Server Components scan directories for `page.tsx` files and render grouped navigation UIs with switchable display variants.

## Architecture

```
Server Component (RouteIndexPage)
  └─ scanRoutes() reads filesystem (server-only)
  └─ Serializes RouteDisplayData
  └─ Passes to RouteDisplaySwitcher (client)
       └─ next/dynamic loads active variant (ssr: false)
       └─ Dropdown switches between variants
```

**Key boundary:** `utils/route-discovery/index.ts` is `server-only` (filesystem access). `utils/route-discovery/shared.ts` has pure functions safe for client imports.

## File Map

| File | Role |
|------|------|
| `utils/route-discovery/index.ts` | Server-only: `scanRoutes`, `scanRoutesShallow` |
| `utils/route-discovery/shared.ts` | Client-safe: `groupRoutes`, `getRouteLabel`, `toModulePages`, `sortGroupKeys` |
| `components/ssr/RouteIndexPage.tsx` | Server Component: scans filesystem, renders page shell, passes data to switcher |
| `components/ssr/RouteHeaderData.tsx` | Server Component: auto-feeds `ModuleHeader` nav from filesystem |
| `components/ssr/route-display/types.ts` | `RouteDisplayData`, `RouteDisplayVariant`, `RouteDisplayProps` |
| `components/ssr/route-display/RouteDisplaySwitcher.tsx` | Client wrapper: variant dropdown + `next/dynamic` imports |
| `components/ssr/route-display/GroupedCardsDisplay.tsx` | Variant: grouped folder cards (default) |
| `components/ssr/route-display/DataTableDisplay.tsx` | Variant: searchable keyboard-navigable grid |
| `components/ssr/route-display/ExpandableSectionsDisplay.tsx` | Variant: collapsible accordion sections |
| `components/ssr/route-display/FlatListDisplay.tsx` | Variant: minimal filterable list |

## Usage: Index Page

Replace any hard-coded route listing with:

```tsx
import { join } from "path";
import { RouteIndexPage } from "@/components/ssr/RouteIndexPage";

export default async function MyPage() {
  return (
    <RouteIndexPage
      directory={join(process.cwd(), "app", "(authenticated)", "my-feature")}
      basePath="/my-feature"
      title="My Feature"
    />
  );
}
```

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `directory` | `string` | required | Absolute path to scan |
| `basePath` | `string` | required | URL prefix for links |
| `title` | `string` | — | Page heading |
| `description` | `string` | auto-generated | Subtitle text |
| `icon` | `ComponentType` | `LayoutGrid` | Lucide icon for heading |
| `shallow` | `boolean` | `false` | Scan one level only |
| `defaultVariant` | `RouteDisplayVariant` | `"grouped-cards"` | Initial display variant |
| `children` | `ReactNode` | — | Custom content above the grid |

## Usage: Layout with Auto-Header

Replace `config.ts` + `ModuleHeader` patterns:

```tsx
import { join } from "path";
import { RouteHeaderData } from "@/components/ssr/RouteHeaderData";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <RouteHeaderData
      directory={join(process.cwd(), "app", "(authenticated)", "tests", "forms")}
      moduleHome="/tests/forms"
      moduleName="Forms"
    >
      {children}
    </RouteHeaderData>
  );
}
```

## Adding a New Display Variant

1. Create `components/ssr/route-display/MyNewDisplay.tsx`:

```tsx
"use client";
import type { RouteDisplayProps } from "./types";

export default function MyNewDisplay({ data }: RouteDisplayProps) {
  const { routes, groups, sortedGroupKeys, basePath, hasGroups } = data;
  // Render using data — import from shared.ts, NOT from index.ts
  // Use Link from "next/link" for navigation
}
```

2. Add the variant key to `types.ts`:

```ts
export type RouteDisplayVariant =
  | "grouped-cards"
  | "data-table"
  | "expandable-sections"
  | "flat-list"
  | "my-new-variant";
```

3. Add the label to `VARIANT_LABELS` in `types.ts`.

4. Register in `RouteDisplaySwitcher.tsx`:

```ts
"my-new-variant": dynamic(() => import("./MyNewDisplay"), { ssr: false }),
```

## Scanner Behavior

- Recursively walks directories looking for `page.tsx` files
- Skips: `_prefixed` directories, `[dynamic]` route segments
- Groups routes by top-level directory segment
- `scanRoutesShallow` only checks immediate children (no recursion)

## Converting Existing Routes

When converting a directory from hard-coded `config.ts` to auto-discovery:

1. Replace `page.tsx` with `RouteIndexPage` usage
2. Replace `layout.tsx` with `RouteHeaderData` (if it imported from `config.ts`)
3. Delete `config.ts`
4. Keep `EntityPack` wrappers and `export const dynamic = 'force-dynamic'` if present
5. Never touch functional leaf pages — only replace listing/index pages

## Favicon Integration

`RouteIndexPage` automatically resolves the favicon for the `basePath` and threads it into the display:

- The favicon badge renders next to the page `<h1>` title (replaces the Lucide icon when a favicon exists)
- `GroupedCardsDisplay` uses the favicon color as a left-border accent on each group card
- Flat list entries show the favicon badge instead of a plain dot

No extra code needed — just pass the correct `basePath` and the system picks up the config from `navigation-links.tsx` or the system-route overrides (`/demo`, `/tests`, `/administration`).

To understand how favicons are registered and what colors/letters are already in use, see `.claude/skills/route-metadata-favicons/SKILL.md`.

---

## Pages NOT Using This System

These have custom logic and intentionally remain hand-maintained:

- `administration/page.tsx` — curated category dashboard (has `/administration/all-routes` companion)
- `dashboard/page.tsx` — iOS-style icon grid
- `tests/links/` and `tests/windows/` — master cross-directory index lists
- `demos/local-tools/page.tsx`, `demos/scraper/page.tsx` — custom functional UIs

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…