๐ Simple and lightweight typed MobX router ๐
Uses path-to-regexp power for path matching
import { createRoute } from "mobx-route";
const userDetails = createRoute("/users/:id");
// Path params are required โ TypeScript enforces it
await userDetails.open({ id: 1 });
userDetails.isOpened; // true
userDetails.params; // { id: "1" } โ fully typedBuild route trees naturally โ no config arrays, no <Routes> wrappers:
const users = createRoute("/users");
const userDetails = users.extend("/:userId");
const userPhotos = userDetails.extend("/photos");
// Path is auto-concatenated: /users/:userId/photos
await userPhotos.open({ userId: 42 });
// โ /users/42/photos
users.isOpened; // true (parent is open too)
users.hasOpenedChildren; // trueProtect routes with beforeOpen โ cancel navigation or redirect:
const dashboard = createRoute("/dashboard", {
beforeOpen: async () => {
if (!await isAuthenticated()) {
return { url: "/login", replace: true }; // redirect
}
// return undefined โ proceed
},
checkOpened: () => currentUser.isAuthorized, // reactive predicate
});Same .open() / .close() / .isOpened API โ but no URL involved:
const authModal = createVirtualRoute({
checkOpened: (route) => route.query.data.modal === "auth",
open: (_, route) => route.query.update({ modal: "auth" }),
close: (route) => route.query.update({ modal: undefined }),
beforeClose: () => !hasUnsavedChanges, // prevent closing
});
authModal.isOpened; // reactive โ auto-updates from query
authModal.isClosing; // for exit animationsconst search = createRoute<
"/search",
{},
{},
{ q: string; page?: number; sort?: "asc" | "desc" }
>("/search");
// TQueryParams types the INPUT โ what you pass to open()
await search.open({}, { query: { q: "mobx", page: 1 } });
// query.data is always Record<string, string> at runtime (values come from URL)
search.query.data.q; // string
search.query.data.page; // string | undefined โ use Number() or QueryParam for typed accessReplace params without polluting browser history:
await userRoute.open({ userId: 1 }, { query: { tab: "profile" } });
await userRoute.update({ userId: 2 });
// โ /users/2?tab=profile (replace: true, mergeQuery: true by default)import { RouteView, RouteViewGroup, Link } from "mobx-route/react";
// Declarative route rendering
<RouteView route={userRoute} view={UserPage} fallback={<Loading />} />
// Route switching with fallback
<RouteViewGroup otherwise={notFoundRoute}>
<RouteView route={homeRoute} view={HomePage} />
<RouteView route={userRoute} view={UserPage} />
<div>Not found</div>
</RouteViewGroup>
// Type-safe links
<Link to={userRoute} params={{ userId: 42 }}>Profile</Link>import { RouteViewModel } from "mobx-route/view-model";
class UserPageVM extends RouteViewModel<typeof userRoute> {
route = userRoute;
// payload, pathParams, query, isMounted โ all built-in
}// Optional segment
const route = createRoute("/users{/:tab}");
route.open(); // โ /users
route.open({ tab: 1 }); // โ /users/1
// Wildcard/rest params
const docs = createRoute("/docs/*rest");
docs.open({ rest: ["api", "v2", "auth"] }); // โ /docs/api/v2/authOnly pay for what you use:
import { createRoute } from "mobx-route"; // core only
import { RouteView, Link } from "mobx-route/react"; // + React
import { RouteViewModel } from "mobx-route/view-model"; // + VMnpm install mobx-route
# or
pnpm add mobx-route
# or
yarn add mobx-routePeer dependencies (React integration is optional):
npm install mobx
# For React:
npm install mobx-react-lite react react-domWant to contribute? Follow this guide
