@akshar-technosoft/router
v1.0.1
Published
A complete end to end package made for handling complexity of routing of react application
Readme
@akshar-technosoft/router
⚡ A thin, fully-typed layer over React Router. Register your route tree once — get compile-time-checked paths, typed route params, and IntelliSense on every
Link,NavLink,Navigate, and navigation call across your app. Zero runtime overhead: the typing is erased at build time.
📦 Installation
npm install @akshar-technosoft/router
# or
yarn add @akshar-technosoft/routerreact and react-router are peer dependencies — install them in your app.
🚀 Why this package?
- ✅ Type-safe paths —
to="/oders"(typo) is a compile error. Autocomplete offers every real route. - 🧩 Typed route params —
to="/orders/:id"requiresparams: { id }, checked and inferred from the path. - 🔗 Real anchors —
TypedLink/TypedNavLinkrender<a href>, so ctrl/cmd/middle-click opens a new tab natively. - 🧭 One typed navigate hook —
typedNavigate(path, opts),typedNavigate(-1), ortypedNavigate({ pathname }). - 🪶 No factories, no glue — register the route union once with module augmentation; import components directly.
- 🌐 URL builder —
buildPathinterpolates:paramsand appendsquery, with encoding and validation.
🧩 Setup (three steps)
1. Define your route tree
Use as const satisfies readonly TypedRouteObject[] so path strings stay literal (not widened to string) — this is what lets the types read your paths.
// src/routes.tsx
import { TypedRouteObject, FlattenRoutes } from "@akshar-technosoft/router";
const routes = [
{ path: "/", element: <Home /> },
{
path: "/orders",
children: [
{ path: ":id", element: <OrderDetail /> },
],
},
{ path: "/login", element: <Login /> },
] as const satisfies readonly TypedRouteObject[];
// The union of every reachable full path:
export type AppRoutes = FlattenRoutes<typeof routes>;
// ^? "/" | "/orders" | "/orders/:id" | "/login"
export default routes;2. Register the route union (once)
Augment the package's Register interface in a .d.ts (or any module file). Every typed API in the package now knows your routes — no factories, no per-app wrappers.
// src/types/router.d.ts
import type { AppRoutes } from "@/routes";
declare module "@akshar-technosoft/router" {
interface Register {
routes: AppRoutes;
}
}Without this augmentation the package still works — route args just fall back to plain
string(untyped).
3. Mount the router
// src/main.tsx
import { createBrowserRouter, RouterProvider } from "react-router";
import { toRouteObjects } from "@akshar-technosoft/router";
import routes from "@/routes";
const router = createBrowserRouter(toRouteObjects(routes));
createRoot(document.getElementById("root")!).render(<RouterProvider router={router} />);📖 API
<TypedLink>
React Router <Link> with a route-checked to. Renders a real <a href>.
import { TypedLink } from "@akshar-technosoft/router";
<TypedLink to="/orders">All orders</TypedLink>
// Parameterized route — `params` is required and typed from the path:
<TypedLink to="/orders/:id" routeOptions={{ params: { id: 42 } }}>
Open order
</TypedLink>
// With query string:
<TypedLink to="/orders" routeOptions={{ query: { status: "open", page: 2 } }}>
Open orders
</TypedLink>
// ❌ Compile errors:
<TypedLink to="/order" /> // not a route
<TypedLink to="/orders/:id" /> // missing params.idAll other Link props (className, state, replace, onClick, …) pass through.
<TypedNavLink>
React Router <NavLink> with a route-checked to. When className is a string, an active class (bg-muted-foreground/30) is appended automatically. Pass a callback for full control.
import { TypedNavLink } from "@akshar-technosoft/router";
<TypedNavLink to="/orders" className="px-3 py-2">Orders</TypedNavLink>
<TypedNavLink
to="/orders"
className={({ isActive }) => (isActive ? "font-bold text-primary" : "text-muted-foreground")}
>
Orders
</TypedNavLink><TypedNavigate>
Declarative redirect — React Router <Navigate> with a route-checked to. Defaults to replace (redirects usually should not add a history entry).
import { TypedNavigate } from "@akshar-technosoft/router";
function Protected({ user, children }) {
if (!user) return <TypedNavigate to="/login" />;
return children;
}
// Push instead of replace:
<TypedNavigate to="/orders/:id" routeOptions={{ params: { id } }} replace={false} />useTypedNavigate()
Typed wrapper around useNavigate. Returns { typedNavigate }, which accepts three kinds of target.
import { useTypedNavigate } from "@akshar-technosoft/router";
function Example() {
const { typedNavigate } = useTypedNavigate();
// 1. A route path — params/query typed and inferred:
typedNavigate("/orders/:id", { params: { id: 42 }, query: { tab: "items" } });
// 2. History delta:
typedNavigate(-1); // back
typedNavigate(1); // forward
// 3. An explicit location object:
typedNavigate({ pathname: "/orders", search: "?status=open" }, { replace: true });
// Redirect after an action (replace so Back doesn't return to the form):
typedNavigate("/orders", { replace: true, state: { justCreated: true } });
}Route options: params, query, replace, state.
buildPath(path, params?, query?)
Build a URL string from a pattern. Interpolates :params (URI-encoded) and appends query. Throws if a params key has no matching :param.
import { buildPath } from "@akshar-technosoft/router";
buildPath("/orders/:id", { id: 42 }, { tab: "items" }); // "/orders/42?tab=items"
buildPath("/orders"); // "/orders"toRouteObjects(routes)
Casts a TypedRouteObject[] tree to plain React Router RouteObject[] for createBrowserRouter/useRoutes. Runtime no-op.
🧠 Exported types
| Type | Purpose |
|------|---------|
| TypedRouteObject<Path> | A RouteObject with a literal-typed path. Define routes with this. |
| FlattenRoutes<T> | Union of every full path reachable in a route tree. FlattenRoutes<typeof routes>. |
| PathParams<Path> | Object type of the :params in a path. PathParams<"/orders/:id"> → { id: string }. |
| QueryParams | Record<string, string \| number \| boolean> for query entries. |
| RouteContainsParams<Path> | true/false — whether a path has any :param. |
| Register | The interface you augment to register your route union. |
| RegisteredRoutes | Resolves to your route union after augmentation (else string). |
🔁 Migrating from the factory-based API
This release replaces the factory-based API with module augmentation, and drops unused features. Breaking changes:
- Removed
createTypedComponents/createTypedHooksfactories. ImportTypedLink,TypedNavLink,TypedNavigate,useTypedNavigatedirectly from the package, and register routes via theRegisterinterface (see Setup). - Removed link preloading (
preload,preloadOn) andinjectRoutes/getRouteMeta/ route-meta map (was a no-op). - Removed
typedReplace,getQueryParams,currentPathfrom the navigation hook. UsetypedNavigate(path, { replace: true }),new URLSearchParams(location.search), anduseLocation()respectively. useTypedNavigate()now returns only{ typedNavigate }.
Migration is mechanical: delete the factory glue file, add the Register augmentation, and switch imports to the package.
License
MIT © Akshar Technosoft
