Getting Started
tss-router is a small, type-safe router for React. Routes are declared with a fluent builder, so the path you write is the source of truth: TypeScript infers the shape of params (path placeholders + the optional $search map), and Link / useNavigate enforce that you pass the right keys at the call site.
Installation
npm install tss-route-libpnpm add tss-route-libyarn add tss-route-libreact (>=19) is a peer dependency. The library has no other runtime dependencies — its browser/memory history implementations ship with the package.
Quick start
import {createBrowserHistory, route, routingHooksFactory} from 'tss-route-lib';
const router = route('home', '/', () => <div>Home</div>)
.at('user', '/users/:id', (params) => <div>User {params.id}</div>)
.at('search', '/search?q=q', (params) => <div>Searching: {params.$search.q ?? '(none)'}</div>);
// One factory call returns everything you need: Provider, hooks, and Link.
const {RouteProvider, useRouter, useNavigate, Link} = routingHooksFactory(router);
function App() {
const view = useRouter();
const navigate = useNavigate();
return (
<div>
<nav>
<Link route="home">Home</Link>
<Link route="user" params={{id: '123'}}>User 123</Link>
<button type="button" onClick={() => navigate('search', {$search: {q: 'shoes'}})}>
Search shoes
</button>
</nav>
<main>{view}</main>
</div>
);
}
function Main() {
return (
<RouteProvider history={createBrowserHistory()}>
<App />
</RouteProvider>
);
}How the factory call works
routingHooksFactory(router) is the single binding point between your router definition and your components. It returns:
RouteProvider— wraps the app and supplies history to descendant hooksuseRouter()— renders the route matching the current locationuseMatch()/useMatch(key)— read the active route or test a specific oneuseNavigate()/useRedirect()— programmatic navigationLink— type-safe<a>
Because the router lives inside the factory's closure, components never import the router itself — they import the hooks/components from wherever you keep your factory output. This avoids circular imports between routes.tsx and the components rendered by your routes.
What you get from this snippet
<Link route="user" params={...}>errors at compile time ifidis missing.navigate('search', {$search: {q: 'shoes'}})knows thatqis the only allowed key.- The render function for each route receives
paramstyped exactly to that path. - Path values are URL-encoded automatically; search values go through
URLSearchParams.
Continue with Defining routes for a deeper look at the builder.