A simple, type-safe router for React that works with any framework.
- ✨ Super Simple - Only 2 functions:
createRouteandcreateRouter - 🔒 Type Safe - TypeScript knows your route params automatically
- 🎯 Flexible - Works with any React framework
- ✅ Validated - Built-in query parameter validation
npm install @btst/yar
# or
pnpm add @btst/yarHere's one complete example showing all features:
import { createRoute, createRouter } from "@btst/yar";
import { z } from "zod"; // or any Standard Schema library
import HomePage from "./pages/home";
import BlogPostPage from "./pages/blog-post";
// 1️⃣ Simple route - just a page
const homeRoute = createRoute(
"/",
() => ({
PageComponent: HomePage,
meta: () => [{ name: "title", content: "Home" }],
}),
undefined,
{ isStatic: true } // 🏷️ Optional: tag routes for filtering for SSG environments
);
// 2️⃣ Dynamic route - with params, validation, and data loading
const blogRoute = createRoute(
"/blog/:slug", // :slug becomes available as params.slug
({ params, query }) => ({
PageComponent: BlogPostPage,
// 📦 Load data (can use AbortSignal, options, etc.)
loader: async (signal?: AbortSignal) => {
const res = await fetch(`/api/posts/${params.slug}`, { signal });
return res.json();
},
// 📄 Generate meta tags (can use loader data)
meta: (post) => [
{ name: "title", content: post.title },
{ name: "description", content: post.excerpt },
],
// 🎨 Extra data for anything (breadcrumbs, layout, etc.)
extra: () => ({
breadcrumbs: ["Home", "Blog", params.slug],
layout: "blog",
}),
}),
{
// ✅ Validate query parameters
query: z.object({
preview: z.boolean().optional(),
}),
},
{ isStatic: false, requiresAuth: false } // 🏷️ Route tags
);
// 3️⃣ Create router
const router = createRouter({
home: homeRoute,
blog: blogRoute,
});
// 4️⃣ Use the router in your app
async function handleRequest(url: string) {
const route = router.getRoute(url);
if (!route) {
return <NotFoundPage />;
}
// Everything is typed! TypeScript knows the types of all params
const data = route.loader ? await route.loader() : null;
const metaTags = route.meta ? route.meta(data) : [];
const extras = route.extra ? route.extra() : null;
return (
<route.PageComponent
params={route.params}
data={data}
/>
);
}
// 5️⃣ Filter routes without running handlers (great for SSG!)
const staticRoutes = Object.values(router.routes)
.filter(route => route.meta?.isStatic);Creates a route. The handler returns:
PageComponent- Your React componentloader()- Load data (can accept any params likeAbortSignal)meta()- Generate SEO tags (can accept any params)extra()- Any extra data you need (can accept any params)
The 4th parameter routeMeta lets you tag routes for filtering (e.g., { isStatic: true }).
Combines your routes. Returns:
routes- All your routesgetRoute(path, query?)- Match a URL and get the route
🎯 Path Parameters
"/blog/:slug" → params.slug is automatically typed✅ Query Validation
query: z.object({ sort: z.string() })🏷️ Route Tags
{ isStatic: true, requiresAuth: false }
// Filter without running handlers - perfect for finding static routes in SSG environments, or for filtering routes that require authentication.🎨 Flexible Functions
loader: (signal) => fetch(url, { signal })
meta: (data) => [{ name: "title", content: data.title }]
extra: (userId) => ({ breadcrumbs: [...], userId })Perfect for prefetching data and generating meta tags in SSR environments, or for adding extra data to your routes.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT