npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

route-sage

v2.0.1

Published

A TypeScript utility for managing and configuring routes with type safety and nested route support

Readme

Route Sage 🧙‍♂️

NPM Version License: ISC

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

  1. Installation
  2. Core Features
  3. API Reference
  4. CLI Tools
  5. Configuration
  6. Examples
  7. Advanced Usage

Installation

npm install route-sage
# or
yarn add route-sage
# or
pnpm add route-sage

Core 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 object
  • reduceCallback: 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 --help

Configuration 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 -w

Watch 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 rootFolder for 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 -w

Programmatic 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/watch in 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 excludeFolderNames and ignoreFolders config

Dynamic Route Parameters

The CLI automatically handles dynamic route parameters using bracket notation:

  • [id] folder becomes $id parameter
  • [userId] folder becomes $userId parameter
  • 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); // true

Working 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"]); // true

Helper 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

  1. Organize Routes by Feature: Group related routes together in logical hierarchies
  2. Use Descriptive Names: Choose clear, descriptive names for your routes
  3. Leverage TypeScript: Take advantage of type safety for parameters and return types
  4. Consistent Naming: Use consistent naming conventions across your route structure
  5. Documentation: Document complex route hierarchies and parameter requirements

Troubleshooting

Common Issues

  1. Type Errors: Ensure your route configuration matches the expected interface
  2. Missing URLs: Every route function must return an object with a url property
  3. 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 start

Contributing

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.