Documentation
Sirou
Define routes once. Navigate everywhere.
Sirou is a universal type safe routing engine for the modern JS/TS ecosystem. Write your route schema once, get compile time safety, async route guards, data loaders, deep link generation, and a built in DevTools panel all working identically across React, Next.js, Svelte, and React Native.
Why Sirou?
| Problem | Sirou Solution |
|---|---|
| Typos in route names crash at runtime | TypeScript catches them at compile time |
Route params are untyped string | Full param type inference & validation per route |
| Different navigation APIs on every platform | One router.go() call works everywhere |
| No way to protect routes without boilerplate | Async composable guards register once, run everywhere |
| Data fetching is totally separate from routing | Integrated Data Loaders pre fetch before render |
| No visibility into navigation state | Built in DevTools panel with history, params, and meta |
Packages
| Package | Version | Description |
|---|---|---|
@sirou/core | Core engine, Radix Trie matcher, type system, guards | |
@sirou/react | React adapter, hooks, DevTools, Data Loaders | |
@sirou/next | Next.js App Router + Middleware adapter | |
@sirou/svelte | SvelteKit adapter with reactive stores | |
@sirou/react-native | React Navigation v6+ integration |
Installation
# Core engine (required)
npm install @sirou/core
# Pick your framework adapter
npm install @sirou/react # React + React Router
npm install @sirou/next # Next.js App Router
npm install @sirou/svelte # SvelteKit
npm install @sirou/react-native # React Native (React Navigation v6+)
Quick Start
1 Define your routes
// src/routes.ts
import { defineRoutes } from "@sirou/core";
export const routes = defineRoutes({
home: {
path: "/",
meta: { title: "Home" },
},
user: {
path: "/user/:id",
params: { id: "string" },
meta: { title: "User Profile", requiresAuth: true },
guards: ["auth"], // named guard, registered later
},
products: {
path: "/products",
children: {
details: {
path: "/:productId",
params: { productId: "string" },
loader: async ({ params }) => {
const res = await fetch(`/api/products/${params.productId}`);
return res.json();
},
},
},
},
notFound: { path: "*" },
});
2 Set up the provider
// React (src/main.tsx)
import { BrowserRouter } from "react-router-dom";
import { SirouRouterProvider } from "@sirou/react";
import { routes } from "./routes";
import { authGuard } from "./guards";
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<SirouRouterProvider config={routes} guards={[authGuard]}>
<App />
</SirouRouterProvider>
</BrowserRouter>,
);
// Next.js (app/layout.tsx)
import { SirouRouterProvider } from "@sirou/next";
import { routes } from "@/routes";
export default function Layout({ children }) {
return (
<SirouRouterProvider config={routes} guards={[authGuard]}>
{children}
</SirouRouterProvider>
);
}
3 Navigate with full type safety
import { useSirouRouter } from "@sirou/react";
function Nav() {
const router = useSirouRouter<typeof routes>();
return (
<nav>
<button onClick={() => router.go("home")}>Home</button>
{/* TypeScript error if 'id' is missing or wrong type */}
<button onClick={() => router.go("user", { id: "abc123" })}>
Profile
</button>
</nav>
);
}
Route Guards
Guards are async functions that run before navigation. They can allow, block, or redirect.
// src/guards.ts
import { RouteGuard } from "@sirou/core";
export const authGuard: RouteGuard = {
name: "auth",
execute: async ({ meta }) => {
const token = localStorage.getItem("token");
if (!token && meta?.requiresAuth) {
return { allowed: false, redirect: "/login" };
}
return { allowed: true };
},
};
Guards are attached to routes by name and registered on the provider:
// Guards on a route
user: {
path: "/user/:id",
guards: ["auth"], // guard names
}
// Register guards on the provider
<SirouRouterProvider config={routes} guards={[authGuard]}>
Guard on Initial Load
When the app first loads, Sirou checks guards on the initial route too not just on navigation. This prevents users from deep linking directly to protected pages.
Next.js Middleware Guard
For server side protection (SSR / RSC), use createSirouMiddleware:
// middleware.ts
import { createNextMiddleware } from "@sirou/next/server";
import { routes } from "@/routes";
import { authGuard } from "@/guards";
export const middleware = createSirouMiddleware(routes, [authGuard]);
export const config = {
matcher: ["/user/:path*", "/dashboard/:path*"],
};
Data Loaders
Attach a loader to any route. The data is pre fetched before the route renders and accessible via useRouteData.
details: {
path: "/:productId",
params: { productId: "string" },
loader: async ({ params }) => {
return fetch(`/api/products/${params.productId}`).then(r => r.json());
},
},
import { useRouteData } from "@sirou/react";
function ProductDetails() {
const product = useRouteData<{ name: string; price: number }>();
return (
<h1>
{product?.name} ${product?.price}
</h1>
);
}
Features at a Glance
| Feature | API |
|---|---|
| Type safe navigation | router.go("routeName", params) |
| Type safe URL builder | router.build("routeName", params) |
| Route param validation | params: { id: "string" } in config |
| Route guards | guards: ["guardName"] + router.registerGuard() |
| Initial route protection | router.initialize() called automatically on mount |
| Data loaders | loader: async (ctx) => data in config |
| Route history | router.getHistory() |
| Wildcard / 404 routes | notFound: { path: "*" } |
| Deep link generation | generateSirouLinking(config, prefixes) (React Native) |
| Analytics tracking | useRouteAnalytics(callback) |
| Auto document title | useRouteTitle("Fallback") |
| Breadcrumb generation | useBreadcrumbs() |
| DevTools panel | <SirouDevTools /> |
| Schema validation | new SchemaValidator().validate(routes) |
React Native Deep Links
Sirou automatically generates a React Navigation linking config from your route schema:
import { generateSirouLinking } from "@sirou/react-native";
import { routes } from "./routes";
const linking = generateSirouLinking(routes, ["myapp://"]);
// <NavigationContainer linking={linking}>
DevTools
Add <SirouDevTools /> anywhere in your React app. A floating button appears in the bottom right corner. Click it to open a panel showing the current route, navigation history, params, and metadata in real time.
// Only in development
{
process.env.NODE_ENV === "development" && <SirouDevTools />;
}
Schema Validation
Validate your route config at startup to catch mistakes early:
import { SchemaValidator } from "@sirou/core";
import { routes } from "./routes";
const { valid, errors } = new SchemaValidator().validate(routes);
if (!valid) {
console.error("Route config errors:", errors);
}
Catches: duplicate paths, params defined but missing from path, and path params without a matching params config.
Troubleshooting
Guards not firing?
- Always navigate via
router.go()or<SirouLink>. Sirou cannot intercept native<a>clicks (except in Svelte viasetupSirouGuards). - Make sure guards are registered before the first render (pass them to the provider's
guardsprop).
Initial route not guarded?
initialize()is called automatically by the React / React Native providers. For Svelte / custom setups, callrouter.initialize()manually after attaching guards.
Redirect loop in Svelte?
- Make sure you're using
setupSirouGuardsit uses the_sirouNavigatingflag to preventbeforeNavigatefrom re running after Sirou callsgoto().
Documentation
Full docs are available at sirou.vercel.app and in the docs/ folder of this repository.
Contributing
Contributions are welcome! Please read CONTRIBUTING.md and open a pull request.
Community
- Website: sirou.vercel.app
- Contact: elnatansamuel25@gmail.com
- Issues: GitHub Issues
License
MIT © Sirou Contributors
