Skip to content

react-routes-forge

Type-safe route definitions, automatic path builders, query parameter handling, and active route matching for React applications with zero duplication.

One source of truth for your routes — templates for <Route path={...} /> and typed builders for navigation — with no duplication and no manual string concatenation.

License: MITTypeScript strictNode.js 18+ESM + CommonJS

🚀 Try it live — see react-routes-forge in action: react-routes-forge POC demo

What is react-routes-forge?

react-routes-forge is a tiny, dependency-free toolkit that turns a plain route map into a fully typed object of paths and path builders. Each route key serves double duty:

  • The template — passed straight to <Route path={...} />.
  • The builder.build(params) produces a real, type-checked URL for navigation.

Because the template and the builder come from the same key, they can never drift apart.

ts
import { defineRoutes } from "react-routes-forge";

export const PATHS = defineRoutes({
  USERS: {
    ROOT: "/users", // static   → used directly
    EDIT: "/users/edit/:id", // dynamic  → .build() with typed params
  },
} as const);

PATHS.USERS.ROOT; // '/users'
PATHS.USERS.EDIT; // '/users/edit/:id'
PATHS.USERS.EDIT.build({ id: 42 }); // '/users/edit/42'

It plugs straight into React Router with zero configuration, and the React hooks entry adds typed useParams, navigation, active-link matching, and search-param handling — all without dragging react-router into the core package.

Why react-routes-forge?

Most React apps end up with route definitions like this:

ts
// ❌ The common pattern
export const PATHS = {
  USERS: {
    ROOT: "/users",
    DETAILS: "/users/:id",
  },
};

export const userDetailPath = (id) => `/users/${id}`; // hand-written builder

Dynamic routes need a second entry alongside the template — a hand-written function to build the real URL. As the app grows, the two drift apart, and nothing stops the template and the builder from disagreeing.

react-routes-forge collapses both into a single key:

ts
// ✅ One key, two uses
export const PATHS = defineRoutes({
  USERS: {
    ROOT: "/users",
    DETAILS: "/users/:id",
  },
} as const);

PATHS.USERS.ROOT; // '/users'              → static, used directly
PATHS.USERS.DETAILS; // '/users/:id'          → use in <Route path={...} />
PATHS.USERS.DETAILS.build({ id: 42 }); // '/users/42'           → use when navigating

Quick Tour

Define routes once, then consume them everywhere:

tsx
import { Routes, Route, Link } from "react-router-dom";
import { PATHS } from "./paths";

function App() {
  return (
    <Routes>
      {/* templates work directly as strings */}
      <Route path={PATHS.USERS.ROOT} element={<UserList />} />
      <Route path={PATHS.USERS.EDIT} element={<EditUser />} />
    </Routes>
  );
}

function UserList() {
  return (
    <>
      {/* typed builders for navigation */}
      <Link to={PATHS.USERS.EDIT.build({ id: 42 })}>Edit user 42</Link>
    </>
  );
}

React hooks round out the common router tasks:

tsx
import { useActivePath, useRouteParams } from "react-routes-forge/hooks";

function EditUser() {
  const { id } = useRouteParams(PATHS.USERS.EDIT); // typed params, no casting
  const isActive = useActivePath(PATHS.USERS.EDIT); // nav highlighting
  // ...
}

Key Features

  • Single source of truth — no duplicate template/builder pairs to keep in sync
  • Compile-time param safety.build() is typed from the path string itself; missing or misspelled params are TypeScript errors
  • Query string support — built into .build(), no manual URLSearchParams wrangling
  • Hash fragment support — append #hash via the options bag
  • Splat (*) segments — supported across the entire core API, not just the hooks
  • Route validation — development-time warnings for missing /, non-trailing splats, duplicate paths, and static routes shadowed by a dynamic route
  • Typed query parsingextractQueryFromPath() coerces booleans and numbers; useTypedSearchParams() brings it to components
  • Breadcrumbs — automatic breadcrumb generation from your route tree, with per-route label overrides
  • Zero runtime dependencies for the core API — React Router is an optional peer dependency
  • Deep nesting — organize routes into as many nested groups as your app needs
  • React hooksuseRouteParams, useNavigateTo, useResolvedPath, useActivePath, useTypedSearchParams
  • Next.js Integration — Next.js hooks are available under react-routes-forge/next (App Router and Pages Router supported)
  • Separate hooks entry — React hooks live under react-routes-forge/hooks (and react-routes-forge/next), so the core package never pulls in router dependencies.
  • React Router v6 & v7 — hooks work identically with react-router-dom (v6/v7) and react-router (v6/v7); no duplicate-version risk
  • ESM + CommonJS — dual builds with proper exports conditions for bundlers and Node.js require()

Advanced Examples

For enterprise applications, you can nest route groups as deeply as needed. The type system scales with your object structure.

ts
export const PATHS = defineRoutes({
  MARKETING: {
    HOME: "/",
    ABOUT: "/about",
  },
  APP: {
    DASHBOARD: "/app/dashboard",
    ORGANIZATIONS: {
      LIST: "/app/organizations",
      DETAILS: {
        ROOT: "/app/organizations/:orgId",
        SETTINGS: "/app/organizations/:orgId/settings",
        MEMBERS: {
          LIST: "/app/organizations/:orgId/members",
          PROFILE: "/app/organizations/:orgId/members/:memberId",
        }
      }
    }
  }
} as const);

// Types are strictly preserved through any level of nesting
PATHS.APP.ORGANIZATIONS.DETAILS.MEMBERS.PROFILE.build({ 
  orgId: "acme", 
  memberId: "u_123" 
}); 
// → '/app/organizations/acme/members/u_123'

Route Types

Route typeExampleBehaves asGains
StaticHOME: '/'A primitive string (its template).build(query?, options?) — attach query/hash, no params to fill
DynamicDETAILS: '/users/:id'A primitive string (its template).build(params, query?, options?) and .paramNames
SplatFILES: '/files/*'A primitive string (its template).build(params, query?, options?) and .paramNames

Next Steps

Released under the MIT License.