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.
🚀 Try it live — see
react-routes-forgein 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.
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:
// ❌ The common pattern
export const PATHS = {
USERS: {
ROOT: "/users",
DETAILS: "/users/:id",
},
};
export const userDetailPath = (id) => `/users/${id}`; // hand-written builderDynamic 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:
// ✅ 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 navigatingQuick Tour
Define routes once, then consume them everywhere:
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:
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 manualURLSearchParamswrangling - Hash fragment support — append
#hashvia 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 parsing —
extractQueryFromPath()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 hooks —
useRouteParams,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(andreact-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) andreact-router(v6/v7); no duplicate-version risk - ESM + CommonJS — dual builds with proper
exportsconditions for bundlers and Node.jsrequire()
Advanced Examples
For enterprise applications, you can nest route groups as deeply as needed. The type system scales with your object structure.
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 type | Example | Behaves as | Gains |
|---|---|---|---|
| Static | HOME: '/' | A primitive string (its template) | .build(query?, options?) — attach query/hash, no params to fill |
| Dynamic | DETAILS: '/users/:id' | A primitive string (its template) | .build(params, query?, options?) and .paramNames |
| Splat | FILES: '/files/*' | A primitive string (its template) | .build(params, query?, options?) and .paramNames |
Next Steps
- Getting Started — install and first route definition
- defineRoutes — start with the core API
- React Hooks — typed hooks for React Router integration
- Next.js Integration — App Router & Pages Router hooks and patterns
- Query & Hash Support — query strings and hash fragments
- Strict Mode — catching missing params at compile time
- Migration Guide — migrate from manual path patterns
- TypeScript Support — type inference details