wouter: a React router that measures itself in kilobytes
🥢 A minimalist-friendly ~2.2KB routing for React and Preact
At a glance
- What is it?
- A router with no top-level provider, three hooks, four familiar components and a size budget enforced in CI, aimed at applications that need URL routing and nothing more from the navigation layer.
- Who is it for?
- wouter is a good fit when your application has a handful of routes and you want the navigation layer to be nearly invisible in your bundle, and a poor fit the moment you need nested layouts, data loaders or the broader React Router ecosystem.
- Can I use it commercially?
- Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 18 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- molefrog/wouter7,911 ★ · +10
| Repository | 2026-10-07 | 2026-10-23 |
|---|---|---|
| molefrog/wouter | 7.9k | 7.9k |
10 Stars in the last 16 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The size claim is enforced, not asserted
The README leads with a size comparison: minimum dependencies, only 2.1 KB gzipped against 18.7 KB for React Router. Numbers like that are cheap to print and expensive to hold, so the interesting part is what holds them in place. The root `package.json` is a monorepo manifest with `workspaces` set to `packages/*` and a script called `size` that runs size-limit.
The size-limit configuration is specific rather than aspirational. It pins `packages/wouter/src/index.js` to a 2500 B limit, and then it does something more useful: it measures the individual location hooks separately. `packages/wouter/src/use-browser-location.js` gets a 1000 B limit with only the `useBrowserLocation` import included, and `packages/wouter/src/memory-location.js` gets the same treatment. Both entries ignore `react` and `use-sync-external-store` so the peer dependencies are not counted against the library.
Measuring the browser hook apart from the package index is a sign that the author understands what a size regression usually looks like. Most of the time it is one new hook growing, not the whole surface growing, and a per-module budget catches that earlier than a single number does. The README badge points at a workflow file named `size.yml` with the label 2.5KB+ limit, which is the CI job enforcing all of this on every change.
The claim in the description line is 2.2 KB, the README says 2.1 KB, and the CI limit is 2500 B. Those are three numbers for roughly the same measurement at different points, and the CI limit is the one that actually matters because exceeding it fails the build.
No top-level Router component, and why that matters
The feature list says there is no top-level `<Router />` component and that it is fully optional. This is the single structural decision that separates wouter from most routers, and it is worth understanding before you install it.
In a router with a required provider, the location state lives in a component above your application, and every consumer reaches it through context. wouter inverts that. Each `<Route>` or hook subscribes to the browser location directly, so you can render a link in one part of the tree and a route in another without them sharing an ancestor.
The consequence for your code is that wouter drops into an existing application without wrapping it. If you are adding routing to a page inside a larger app, or migrating one route at a time out of a bigger router, that is a real advantage. If you are starting fresh and expect to need layout routes, nested routers with their own configuration, or a custom history implementation across many components, the same property becomes a limitation because there is no central place to configure anything.
The README mentions a `<Router hook={hook} parser={fn} base={basepath} />` component in the component API, which is the escape hatch. It takes a location hook, a parser and a base path, so the default browser history can be swapped for a memory history or a custom one when you need it. Fully optional means you do not have to use it, not that it does not exist.
The component surface is deliberately a React Router subset
The README says wouter mimics React Router's best practices by providing familiar components, and lists four: `Route`, `Link`, `Switch` and `Redirect`. That is the whole component API, and the fact that it is a recognisable subset is the design goal rather than a limitation to apologise for.
The getting started example shows the intended shape:
import { Link, Route, Switch } from "wouter";
const App = () => (
<>
<Link href="/users/1">Profile</Link>
<Route path="/about">About Us</Route>
<Switch>
<Route path="/inbox" component={InboxPage} />
<Route path="/users/:name">
{(params) => <>Hello, {params.name}!</>}
</Route>
<Route>404: No such page!</Route>
</Switch>
</>
);Two details carry most of the weight. A `<Route>` with children that is a function receives the matched params, so `:name` in the pattern arrives as `params.name` without a separate hook. And a `<Route>` with no path acts as the fallback inside a `<Switch>`, which is how the 404 line works. `<Switch>` gives exclusive matching, first match wins, and the README notes that routes below it are matched exclusively.
For anyone migrating from React Router this is the migration story in miniature: the import names are the same, the nesting is the same, and what you lose is everything not on that list. Route nesting is documented as a section of the `Route` entry, so it exists in some form rather than being absent.
Three levels of API: low-level, hooks, components
The README describes the API as coming in three kinds, starting with low-level. Above that sit the hooks, and above them the components. Naming that ladder explicitly tells you how to descend when the convenience layer is not enough.
The hook list in the table of contents is where the real surface lives: `useRoute` for route matching and parameters, `useLocation` for working with the history, `useParams` for extracting matched parameters, `useSearch` and `useSearchParams` for query strings, and `useRouter` for accessing the router object.
Two of those deserve comment because they address real gaps. `useLocation` has subsections for additional navigation parameters and for customizing the location hook, which is how you get state through a navigation rather than a query string, and how you replace the history source. `useSearch` and `useSearchParams` are separate, and the distinction is the interesting part: one reads the query string, the other gives you setters, so filtering state can live in the URL properly rather than in a parallel store.
The stated reason hooks exist at all is control over presentation. The README frames `useRoute`, `useLocation` and `useRouter` as hook-based APIs for more granular control over routing, and gives animated route transitions as the example in the FAQ list. When you want a transition, you need to know the previous location and the incoming one at the same time, which a declarative component boundary makes awkward. That is the case the hooks are for.
Preact support, TypeScript minimums and the FAQ list
Two platform statements in the Getting Started section are worth quoting because they are the kind of thing that only matters at install time and then never again. The library targets ES2020+ compatibility, and if you need older browsers you should transpile `node_modules` yourself. The minimum supported TypeScript version is 4.1, required for route parameter inference.
Preact support is real rather than aspirational. The install command differs, `npm i wouter-preact`, and there is a dedicated Preact support section in the FAQ. Given that the topics include both `preact` and `react`, and that the description names both frameworks, this is a dual-target library rather than a React library that happens to work.
The FAQ section is the most underrated part of the README, because a small library earns trust by being honest about its gaps. The entries cover deploying to a subfolder with a base path, making a default route, marking a link active for the current route, strict routes, relative routes and links, initiating navigation from outside a component, TypeScript usage, animated route transitions, adding view transitions, Preact support, server-side rendering, configuring the router to render a specific route in tests, and a FAQ titled 1KB is too much, I cannot afford it.
Reading that list tells you more about the project's maturity than the feature list does. Every one of those is a question a small router gets asked, and having thirteen answered in the repository means you can evaluate the fit before writing code.
Who uses it, and what the repository is for
The README names specific projects that depend on wouter: Ultra, React-three-fiber, Sunmao UI and Million. React-three-fiber is the significant one, because it is a large library with real production use whose routing needs are modest, which is exactly the profile wouter suits. A router adopted by a graphics library author is a stronger signal than the star count.
The repository is a monorepo. Beyond `packages/` there is `specs/`, which is where the behavioural contract for the library lives, `assets/` for the logo and the illustration credited to Katya Simacheva, and a `bun.lock` with a `bunfig.toml`, so the test suite runs on Bun rather than a more conventional runner. The scripts confirm the split: `test` is `bun test`, `test-types` is `tsc --noEmit`, `lint` runs eslint over `packages/**/*.js`, and `size` is the size-limit run described earlier.
Two adjacent files are worth knowing about if you contribute. There is an `AGENTS.md` and a `CLAUDE.md` at the root, so the project ships instructions for coding agents as part of its normal contribution flow rather than treating them as an afterthought. The most recent release is v3.11.0, published on 2026-09-05, and the last push was on 2026-09-14, so the v3 line is being maintained. Licensing is worth checking against the root manifest rather than the README badge: the `package.json` declares ISC.
Editorial conclusion
wouter is a good fit when your application has a handful of routes and you want the navigation layer to be nearly invisible in your bundle, and a poor fit the moment you need nested layouts, data loaders or the broader React Router ecosystem. What makes it trustworthy rather than merely small is that the size claim is a CI gate: size-limit runs in the workflow, and a README table of contents with thirteen recipe entries shows the maintainers have thought about the cases where it does not fit. Install it, read the FAQ section for your specific gap, and if none of those recipes covers your case, that is the signal to use the larger router.
Frequently asked questions
How does wouter compare with React Router?
The API is a deliberate subset. wouter provides Route, Link, Switch and Redirect with familiar semantics, plus hooks for lower level control, and it is roughly 2.1 KB gzipped against about 18.7 KB for React Router. You give up data loaders, nested layouts and the wider ecosystem that ships with the larger router.
Does wouter work with Preact as well as React?
Yes, and Preact is a first-class target rather than an accident. There is a separate install path, npm i wouter-preact, and a dedicated Preact support section in the README FAQ. The package.json marks the library as having no side effects, which also helps tree shaking on either framework.
Do I need a Router component in wouter?
No, there is no top-level Router component and it is fully optional, which the README lists as a feature. Routes and hooks read the location directly, so there is no provider to wrap your application in. A Router component accepting a hook, parser and base path still exists for custom history setups.
What is the name Wouter?
It is the author's handle, molefrog, used as the package name. As a given name, Wouter is a Dutch and Frisian form of Walter, usually rendered in English as Walter or Wouter, and it is unrelated to this router despite search results suggesting otherwise.
How is wouter kept under its size limit?
With size-limit configured in the root package.json, run as a script and enforced by a CI workflow. The package entry point has a 2500 B budget, and the browser and memory location hooks each have their own 1000 B budget measured separately, so growth in one module is caught even when the total looks fine.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/molefrog-wouter)Star history chart for your README
[](https://hysenlabs.com/projects/molefrog-wouter)Was this useful?