Building my own router

synecdokey profile image Emilia ・3 min read

I don't want to use react-router on my personal projects. Partly because of the bloat you incur for the high degree of compatibility (even the shiny version 6 still has a fair amount of cruft), partly because I'd rather not use something that comes from a proud LDS church member. Call me political all you want, but given their track record on queerness, gender and race, I see nothing to be proud about.

I looked at alternatives, but truth be told, no one really wants to reinvent the wheel, and for good reasons! It works, and Facebook is probably a bigger issue if we're talking ethics. But hey, it's nonetheless a fun exercise to get acquainted with what browsers can do nowadays.

It's about leaving things behind

react-router relies on their own history package, because it wants to be compatible with react native and Internet Explorer. I do not need nor want that, so there are a fairly consequential number of wins to be had just there. Though there's a gotcha: the history API can drive events in specific cases, but you'll still need some wrapping in the cases an event is not fired.

One way to do things

react-router affords you flexibility, as it's not opinionated in how you build your routes, but that means that different codebases can look very different. I often need to jump projects at work, and the lack of consistency due to that freedom is a curse I'd rather not deal with. Having a single way to do things is simpler to explain and learn, simpler to use, and simpler to maintain.

Introducing itsy-bitsy-router

An evergreen-browser barebones routing solution that only offers a hook-based API, and a Link component for convenience. There's a documentation website available to get started quickly, and while it's not expansive yet, it does the job quite well, and allows me to dog-food the router with just itself.


I went with a very familiar API. It works, and does the job in a very simple manner. It supports url matching the way you'd expect: path/to/:match/. It's fairly simple to use, and should cater to most use-cases! If not, that's probably something that can be improved, and opening an issue could make a difference.

It's also really lightweight, at around 1kb gzipped. This is especially valuable when considering that a router is going to be part of your initial payload no matter what.


  • As history.pushState() and history.replaceState() do not fire any events, we need the useNavigate() hook to handle navigation in places where Link is not desirable. history.forward() and history.back() do work and trigger rerenders as expected though.
  • TypeScript can't really play nice with something like useParams(), and probably never will. So we're stuck with Record<string, string> as the return type, instead of having something that relates to the current component. I don't see an easy way out of this one given we rely on string-defined properties with the /path/to/:id API pattern. There may be an alternative API, focused on Typescript to be found at some point, but one of the selling points right now is to enable a quick opt-in/opt-out.
  • SSR is something that probably doesn't work as-is (Maybe?), haven't really tried. But with gatsby and next already equipped with their own routing, I'm not really willing to spend time investigating this.
  • There's no support for React Native. That's definitely not something I need, and I want to keep things as lean as I can.

What's next

I want to get a feel for the API as it is, and maybe adjust a few things along the way, but this should be fairly final. The one thing I really want to integrate before cutting a v1 is React's Concurrent Mode, as routing is one of the rare occasions where it really shines.

I mainly did this for myself, and for fun, but I feel like this could go further than that! If you liked it, do let me know ✨


Editor guide