route-sage
v2.0.1
Published
A TypeScript utility for managing and configuring routes with type safety and nested route support
Maintainers
Readme
Route Sage 🧙♂️
Route Sage is a powerful TypeScript utility for managing application routes with full type safety, nested route support, and automatic route generation from folder structures. This package provides both programmatic APIs and CLI tools to streamline route management in modern web applications.
For AI agents: see AGENTS.md — non-interactive CLI, JSON mode, and task recipes.
Table of Contents
Installation
npm install route-sage
# or
yarn add route-sage
# or
pnpm add route-sageCore Features
1. Type-Safe Route Configuration
Create routes with full TypeScript support and IntelliSense autocompletion.
2. Nested Route Hierarchies
Build complex, deeply nested route structures with clean, maintainable code.
3. Automatic Route Generation
Generate route configurations automatically from your folder structure using the CLI.
4. Framework Agnostic
Works with any JavaScript/TypeScript project - React, Vue, Angular, or vanilla JS.
5. Parameter Support
Handle dynamic route parameters with type safety.
6. Root folder Watching & Auto-Regeneration
Watch your project root folders/files for changes and automatically regenerate route configurations when folders/files are added, modified, or deleted.
7. Query strings on route URLs
Every segment from createPathConfig() includes .filter() (chainable query builder) and .href() (pathname or pathname + query in one call), plus low-level appendSearchParams.
8. React Router helpers
sagePathname, sageLocation, sageNestedChildPath, and sageRouteTable (map many routes without repeating <Route>). See Integration with React Router below.
9. Browser vs Node builds
Default route-sage is safe for Vite/browsers (no micromatch / fs). Node-only APIs (generateFromFolder, validateConfig, etc.) import from route-sage/node.
API Reference
Package entry points
| Import | When to use |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| route-sage | Apps and bundlers (Vite, webpack, etc.): createPathConfig, query helpers, React helpers (sagePathname, sageRouteTable, …), and types. Does not load Node-only dependencies. |
| route-sage/node | Node scripts: everything above plus generateFromFolder, generateRouteBundle, generateFolderJson, validateConfig, and inspectRoutes. |
| route-sage/agent | Agent tooling: inspectRoutes, stable error codes, mapping rules, JSON CLI helpers (Node-only). |
| route-sage/react-router | Same React Router helpers as the main entry, if you prefer a subpath import. |
| route-sage/schema | JSON Schema for route-sage.config.json. |
Core Functions
createPathConfig<T>(config: T): EnrichedRouteMap<T>
Creates a type-safe route configuration object from your route definitions. Each segment gets .url, .filter(), and .href() (see JSDoc on the published types).
Parameters:
config: An object defining your route structure with nested routes and parameters
Returns:
- A transformed route configuration with computed URLs and type safety
Example:
import { createPathConfig } from "route-sage";
const routes = createPathConfig({
home: () => ({ url: "/" }),
about: () => ({ url: "/about" }),
contact: () => ({ url: "/contact" }),
});
// Usage
const homeUrl = routes.home().url; // "/"
const aboutUrl = routes.about().url; // "/about"transformPathConfig<T>(paths: T, reduceCallback?: Function): T
Low-level function for transforming path configurations with custom logic.
Parameters:
paths: Route configuration objectreduceCallback: Optional custom transformation function
Returns:
- Transformed route configuration
Type Definitions
IPathConfig<T>
interface IPathConfig<T extends IPathConfigBaseReturn = IPathConfigBaseReturn> {
(...args: any[]): T;
}IPathConfigBaseReturn
You define url (and nested route functions) in your config. After createPathConfig(), each segment also has filter and href for query strings. See the full interface and JSDoc in the published types.
CLI Tools
Route Sage includes a powerful CLI for automatically generating route configurations from your folder structure.
Installation & Setup
The CLI is automatically available after installing the package:
npx route-sage --helpConfiguration File
Create a route-sage.config.json file in your project root:
{
"rootFolder": "src/app",
"excludeFolderNames": ["\\([^)]+\\)"],
"useFiles": false,
"includeFileNames": ["*.tsx", "*.ts"],
"excludeFileNames": ["page.tsx", "layout.tsx"],
"typescript": true,
"ignoreFolders": ["components", "utils"]
}Configuration Options
| Option | Type | Description | Default |
| -------------------- | ---------- | --------------------------------------------------------------------- | ----------------------------- |
| rootFolder | string | Root directory to scan for routes | Required |
| useFiles | boolean | Include files in route generation | false |
| includeFileNames | string[] | File patterns to include | [] |
| excludeFileNames | string[] | File patterns to exclude | [] |
| ignoreFolders | string[] | Folder patterns to ignore completely | [] |
| excludeFolderNames | string[] | Folder patterns to exclude from routes | [] |
| typescript | boolean | Generate TypeScript output | false |
| outputFile | string | Output path without extension (e.g. src/lib/routes or route-sage) | (default stem route-sage) |
| plugins | string[] | Optional CJS plugins that transform generated code | [] |
Other CLI flags: --output / -o (override outputFile), --format (extra json / markdown / yaml), --dry-run, --profile, --no-suggestions, --json (machine-readable stdout; also ROUTE_SAGE_JSON=1).
Subcommands: init (wizard with framework presets), inspect, visualize, lint, migrate. Run npx route-sage --help for full usage.
The generated module (e.g. route-sage.ts) imports createPathConfig and exports the wrapped config so segments include .filter() / .href() without extra setup.
CLI Commands
Generate Routes from Folder Structure
# Use config file settings
npx route-sage
# Specify custom folder
npx route-sage --folder src/pages
# Short form
npx route-sage -f src/pages
# Watch mode - automatically regenerate on file changes
npx route-sage --watch
# Watch with custom debounce delay (default: 500ms)
npx route-sage --watch --debounce 1000
# Short form for watch mode
npx route-sage -wWatch Mode
Route Sage includes a powerful file watching feature that automatically regenerates your route configurations when files are added, modified, or deleted in your project.
⚠️ Note: Watch mode is designed for Node.js environments only (CLI usage during development). The watch functionality is not included in the browser bundle and should not be imported in client-side code.
Features
- Real-time monitoring: Watches your
rootFolderfor any file system changes - Debounced execution: Prevents multiple rapid regenerations with configurable debounce delay
- Smart filtering: Automatically ignores common build artifacts and development files
- Graceful shutdown: Handles Ctrl+C interruption cleanly
CLI Usage
# Start watching for changes
npx route-sage --watch
# Watch with custom debounce delay (milliseconds)
npx route-sage --watch --debounce 1000
# Watch a specific folder
npx route-sage --watch --folder src/pages
# Short form
npx route-sage -wProgrammatic Usage (Node.js Only)
The watch functionality is only available via the CLI. If you need to use it programmatically in a Node.js script, you must import it from the separate watch module:
// Node.js only - DO NOT use in browser/client code
const { RouteSageWatcher } = require("route-sage/dist/watch");
const watcher = new RouteSageWatcher({
rootFolder: "src/app",
config: {
rootFolder: "src/app",
useFiles: false,
typescript: true,
},
debounceMs: 500,
onFileChange: (event, path) => {
console.log(`File ${event}: ${path}`);
},
onError: (error) => {
console.error("Watch error:", error);
},
});
watcher.start();
// Stop watching when done
// watcher.stop();Important: Never import
route-sage/dist/watchin browser/client-side code as it depends on Node.js APIs.
Ignored Patterns
The watcher automatically ignores common patterns:
node_modules/**.git/**dist/**,build/**.next/**,.nuxt/**coverage/**- Files matching your
excludeFolderNamesandignoreFoldersconfig
Dynamic Route Parameters
The CLI automatically handles dynamic route parameters using bracket notation:
[id]folder becomes$idparameter[userId]folder becomes$userIdparameter- Multiple parameters are supported:
[category]/[id]
Example folder structure:
src/app/
├── users/
│ ├── [userId]/
│ │ ├── profile/
│ │ └── settings/
│ └── list/
└── posts/
└── [postId]/
└── comments/
└── [commentId]/Generated route config:
import { IPathConfig, IPathConfigBaseReturn } from "route-sage";
const config = {
users: () => ({
url: `/users`,
list: () => ({
url: `/list`,
}),
$userId: (userId: string) => ({
url: `/${userId}`,
profile: () => ({
url: `/profile`,
}),
settings: () => ({
url: `/settings`,
}),
}),
}),
posts: () => ({
url: `/posts`,
$postId: (postId: string) => ({
url: `/${postId}`,
comments: () => ({
url: `/comments`,
$commentId: (commentId: string) => ({
url: `/${commentId}`,
}),
}),
}),
}),
} satisfies Record<string, IPathConfig<IPathConfigBaseReturn>>;Examples
The sections below show route configuration, React Router integration, Next.js links, and query parameters.
Basic Route Configuration
import { createPathConfig } from "route-sage";
const routes = createPathConfig({
// Simple routes
home: () => ({ url: "/" }),
about: () => ({ url: "/about" }),
contact: () => ({ url: "/contact" }),
// Authentication routes
auth: () => ({
url: "/auth",
signin: () => ({ url: "/sign-in" }),
signup: () => ({ url: "/sign-up" }),
forgotPassword: () => ({ url: "/forgot-password" }),
resetPassword: (token: string) => ({
url: `/reset-password/${token}`,
}),
}),
});
// Usage examples
console.log(routes.home().url); // "/"
console.log(routes.auth().signin().url); // "/auth/sign-in"
console.log(routes.auth().resetPassword("abc123").url); // "/auth/reset-password/abc123"Advanced Nested Routes with Parameters
const appRoutes = createPathConfig({
dashboard: () => ({
url: "/dashboard",
users: () => ({
url: "/users",
list: () => ({ url: "/list" }),
create: () => ({ url: "/create" }),
details: (userId: string) => ({
url: `/${userId}`,
edit: () => ({ url: "/edit" }),
delete: () => ({ url: "/delete" }),
posts: () => ({
url: "/posts",
create: () => ({ url: "/create" }),
details: (postId: string) => ({
url: `/${postId}`,
edit: () => ({ url: "/edit" }),
comments: () => ({
url: "/comments",
add: () => ({ url: "/add" }),
details: (commentId: string) => ({
url: `/${commentId}`,
edit: () => ({ url: "/edit" }),
delete: () => ({ url: "/delete" }),
}),
}),
}),
}),
}),
}),
settings: () => ({
url: "/settings",
profile: () => ({ url: "/profile" }),
security: () => ({ url: "/security" }),
notifications: () => ({ url: "/notifications" }),
}),
}),
});
// Usage examples
const usersList = appRoutes.dashboard().users().list().url;
// Result: "/dashboard/users/list"
const userDetails = appRoutes.dashboard().users().details("123").url;
// Result: "/dashboard/users/123"
const editUser = appRoutes.dashboard().users().details("123").edit().url;
// Result: "/dashboard/users/123/edit"
const userPost = appRoutes
.dashboard()
.users()
.details("123")
.posts()
.details("456").url;
// Result: "/dashboard/users/123/posts/456"
const postComment = appRoutes
.dashboard()
.users()
.details("123")
.posts()
.details("456")
.comments()
.details("789").url;
// Result: "/dashboard/users/123/posts/456/comments/789"E-commerce Application Example
const ecommerceRoutes = createPathConfig({
shop: () => ({
url: "/shop",
categories: () => ({
url: "/categories",
list: () => ({ url: "/list" }),
details: (categoryId: string) => ({
url: `/${categoryId}`,
products: () => ({
url: "/products",
details: (productId: string) => ({
url: `/${productId}`,
reviews: () => ({
url: "/reviews",
add: () => ({ url: "/add" }),
details: (reviewId: string) => ({ url: `/${reviewId}` }),
}),
variants: () => ({
url: "/variants",
details: (variantId: string) => ({ url: `/${variantId}` }),
}),
}),
}),
}),
}),
cart: () => ({
url: "/cart",
checkout: () => ({
url: "/checkout",
shipping: () => ({ url: "/shipping" }),
payment: () => ({ url: "/payment" }),
confirmation: () => ({ url: "/confirmation" }),
}),
}),
orders: () => ({
url: "/orders",
list: () => ({ url: "/list" }),
details: (orderId: string) => ({
url: `/${orderId}`,
tracking: () => ({ url: "/tracking" }),
invoice: () => ({ url: "/invoice" }),
return: () => ({ url: "/return" }),
}),
}),
}),
account: () => ({
url: "/account",
profile: () => ({ url: "/profile" }),
addresses: () => ({
url: "/addresses",
add: () => ({ url: "/add" }),
edit: (addressId: string) => ({ url: `/${addressId}/edit` }),
}),
wishlist: () => ({ url: "/wishlist" }),
orderHistory: () => ({ url: "/order-history" }),
}),
});
// Usage examples
const categoryProducts = ecommerceRoutes
.shop()
.categories()
.details("electronics")
.products().url;
// Result: "/shop/categories/electronics/products"
const productReviews = ecommerceRoutes
.shop()
.categories()
.details("electronics")
.products()
.details("laptop-123")
.reviews().url;
// Result: "/shop/categories/electronics/products/laptop-123/reviews"
const checkout = ecommerceRoutes.shop().cart().checkout().payment().url;
// Result: "/shop/cart/checkout/payment"
const orderTracking = ecommerceRoutes
.shop()
.orders()
.details("order-456")
.tracking().url;
// Result: "/shop/orders/order-456/tracking"Integration with React Router
Use sagePathname when you need the path pattern without a query string, sageLocation for full path + search, and sageRouteTable to register many routes from one array.
import { createPathConfig, sageRouteTable } from "route-sage";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
const routes = createPathConfig({
home: () => ({ url: "/" }),
about: () => ({ url: "/about" }),
blog: () => ({
url: "/blog",
post: (slug: string) => ({ url: `/${slug}` }),
}),
contact: () => ({ url: "/contact" }),
});
const pages = sageRouteTable([
{ path: () => routes.home(), element: <HomePage /> },
{ path: () => routes.about(), element: <AboutPage /> },
{ path: () => routes.blog(), element: <BlogPage /> },
{ path: () => routes.blog().post(":slug"), element: <BlogPostPage /> },
{ path: () => routes.contact(), element: <ContactPage /> },
]);
function App() {
return (
<BrowserRouter>
<nav>
<Link to={routes.home().url}>Home</Link>
<Link to={routes.about().url}>About</Link>
<Link to={routes.blog().url}>Blog</Link>
<Link to={routes.contact().url}>Contact</Link>
</nav>
<Routes>
{pages.map(({ pathPattern, element }) => (
<Route key={pathPattern} path={pathPattern} element={element} />
))}
</Routes>
</BrowserRouter>
);
}Imports are also available from route-sage/react-router.
Integration with Next.js App Router
// routes.ts
import { createPathConfig } from "route-sage";
export const routes = createPathConfig({
home: () => ({ url: "/" }),
dashboard: () => ({
url: "/dashboard",
users: () => ({
url: "/users",
details: (userId: string) => ({ url: `/${userId}` }),
}),
}),
});
// In your components
import { routes } from "./routes";
import Link from "next/link";
export function Navigation() {
return (
<nav>
<Link href={routes.home().url}>Home</Link>
<Link href={routes.dashboard().url}>Dashboard</Link>
<Link href={routes.dashboard().users().url}>Users</Link>
</nav>
);
}
// For dynamic routes
export function UserLink({ userId }: { userId: string }) {
return (
<Link href={routes.dashboard().users().details(userId).url}>
View User {userId}
</Link>
);
}Advanced Usage
Custom Route Properties
You can extend routes with additional properties beyond just URLs:
interface CustomRouteConfig extends IPathConfigBaseReturn {
title?: string;
requiresAuth?: boolean;
breadcrumb?: string;
}
const routes = createPathConfig({
home: () => ({
url: "/",
title: "Home Page",
breadcrumb: "Home",
}),
dashboard: () => ({
url: "/dashboard",
title: "Dashboard",
requiresAuth: true,
breadcrumb: "Dashboard",
users: () => ({
url: "/users",
title: "User Management",
requiresAuth: true,
breadcrumb: "Users",
}),
}),
} satisfies Record<string, IPathConfig<CustomRouteConfig>>);
// Access custom properties
const dashboardRoute = routes.dashboard();
console.log(dashboardRoute.title); // "Dashboard"
console.log(dashboardRoute.requiresAuth); // trueWorking with query parameters
Use .filter() and .href() on any segment:
const routes = createPathConfig({
search: () => ({ url: "/search" }),
products: () => ({
url: "/products",
list: () => ({ url: "/list" }),
}),
});
routes.search().filter({ q: "laptops", category: "electronics" }).url;
// "/search?q=laptops&category=electronics"
routes.products().list().href({ page: 2, limit: 20 });
// "/products/list?page=2&limit=20"Route Validation and Guards
interface AuthenticatedRoute extends IPathConfigBaseReturn {
requiresAuth: boolean;
roles?: string[];
}
const protectedRoutes = createPathConfig({
admin: () => ({
url: "/admin",
requiresAuth: true,
roles: ["admin"],
users: () => ({
url: "/users",
requiresAuth: true,
roles: ["admin", "moderator"],
}),
}),
profile: () => ({
url: "/profile",
requiresAuth: true,
}),
} satisfies Record<string, IPathConfig<AuthenticatedRoute>>);
// Route guard helper
function canAccessRoute(route: AuthenticatedRoute, userRoles: string[] = []) {
if (!route.requiresAuth) return true;
if (!route.roles) return true; // Just requires auth, no specific roles
return route.roles.some((role) => userRoles.includes(role));
}
// Usage
const adminRoute = protectedRoutes.admin();
const canAccess = canAccessRoute(adminRoute, ["user", "admin"]); // trueHelper Functions
objectToJsString(obj: any, indentLevel?: number): string
Converts JavaScript objects (including functions) into string representations. Useful for code generation and serialization.
escapeRegExp(string: string): string
Escapes special characters in strings for use in regular expressions.
Best Practices
- Organize Routes by Feature: Group related routes together in logical hierarchies
- Use Descriptive Names: Choose clear, descriptive names for your routes
- Leverage TypeScript: Take advantage of type safety for parameters and return types
- Consistent Naming: Use consistent naming conventions across your route structure
- Documentation: Document complex route hierarchies and parameter requirements
Troubleshooting
Common Issues
- Type Errors: Ensure your route configuration matches the expected interface
- Missing URLs: Every route function must return an object with a
urlproperty - Parameter Types: Make sure parameter types match your usage throughout the application
Debug Mode
Enable debug logging by setting the environment variable:
DEBUG=route-sage npm startContributing
Route Sage is open source and welcomes contributions. Please check the GitHub repository for contribution guidelines.
Contributors
A special thanks to the following contributors for their valuable work on this project:
Support / Donate
If you find Route Sage useful and would like to support its development, thank you — your support helps pay for hosting, CI, and ongoing maintenance.
You can support the project in any of the following ways:
- Sponsor the maintainer on GitHub: https://github.com/sponsors/akintomiwa-fisayo
- Back the project on Open Collective (placeholder): https://opencollective.com/fisayo-akintomiwa
Thank you for considering supporting the project — every bit helps.
License
This project is licensed under the ISC License.
