wouter

🥢 A minimalist-friendly ~1.5KB routing for React and Preact. Nothing else but HOOKS.

3935
100
JavaScript
Wouter — a super-tiny React router (logo by Katya Simacheva)

npm CI Coverage Coverage Edit in StackBlitz IDE
wouter is a tiny router for modern React and Preact apps that relies on Hooks.
A router you wanted so bad in your project!

Features

by Katya Simacheva

🔥🚒 Breaking News!
The first release candidate of [email protected] is out!
It comes packed with new features: nested routing, wildcard patterns, useSearch, useParams, hash location, memory location,
history state and many more.

Give it a try in your projects and tell us what you think. The current version v2 will no longer receive new features,
only bugfixes and security updates will be accepted. Stay tuned for documentation and more details.

developers đź’– wouter

… I love Wouter. It’s tiny, fully embraces hooks, and has an intuitive and barebones API. I can
accomplish everything I could with react-router with Wouter, and it just feels more minimalist
while not being inconvenient.

Matt Miller, An exhaustive React ecosystem for 2020

Wouter provides a simple API that many developers and library authors appreciate. Some notable
projects that use wouter: Ultra,
React-three-fiber,
Sunmao UI, Million and many more.

Table of Contents

Getting Started

Check out this demo app below in order to get started:

import { Link, Route } from "wouter";

const App = () => (
  <div>
    <Link href="/users/1">
      <a className="link">Profile</a>
    </Link>

    <Route path="/about">About Us</Route>
    <Route path="/users/:name">{(params) => <div>Hello, {params.name}!</div>}</Route>
    <Route path="/inbox" component={InboxPage} />
  </div>
);

Supporting IE11 and obsolete platforms

This library uses features like
destructuring assignment
and const/let declarations and doesn’t
ship with ES5 transpiled sources. If you aim to support browsers like IE11 and below → make sure you
run Babel over your node_modules

In addition, Event is also used. To
be able to run successfully on IE browser, a polyfill is required.

Wouter API

Wouter comes with two kinds of APIs: low-level
React Hooks API and more traditional component-based
API similar to React Router’s one.

You are free to choose whatever works for you: use hooks when you want to keep your app as small as
possible or you want to build custom routing components; or if you’re building a traditional app
with pages and navigation — components might come in handy.

Check out also FAQ and Code Recipes for more advanced things like active
links, default routes etc.

The list of methods available

Hooks API:

  • useRoute — shows whether or not current page matches the
    pattern provided.
  • useLocation — allows to manipulate current
    browser location, a tiny wrapper around the History API.
  • useParams — returns the parameters of the current route.
  • useRouter — returns a global router object that
    holds the configuration. Only use it if you want to customize the routing.

Component API:

  • <Route /> — conditionally renders a component based on a pattern.
  • <Link /> — wraps <a>, allows to perfom a navigation.
  • <Switch /> — exclusive routing, only renders the first matched route.
  • <Redirect /> — when rendered, performs an immediate navigation.
  • <Router /> — an optional top-level
    component for advanced routing configuration.

Hooks API

useRoute: the power of HOOKS!

Hooks make creating custom interactions such as route transitions or accessing router directly
easier. You can check if a particular route matches the current location by using a useRoute hook:

import { useRoute } from "wouter";
import { Transition } from "react-transition-group";

const AnimatedRoute = () => {
  // `match` is boolean
  const [match, params] = useRoute("/users/:id");

  return <Transition in={match}>Hi, this is: {params.id}</Transition>;
};

useLocation hook: working with the history

The low-level navigation in wouter is powered by the useLocation hook, which is basically a
wrapper around the native browser location object. The hook rerenders when the location changes and
you can also perform a navigation with it, this is very similar to how you work with values returned
from the useState hook:

import { useLocation } from "wouter";

const CurrentLocation = () => {
  const [location, setLocation] = useLocation();

  return (
    <div>
      {`The current page is: ${location}`}
      <a onClick={() => setLocation("/somewhere")}>Click to update</a>
    </div>
  );
};

All the components including the useRoute rely on useLocation hook, so normally you only need
the hook to perform the navigation using a second value setLocation. You can check out the source
code of the Redirect component as
a reference.

Additional navigation parameters

The setter method of useLocation can also accept an optional object with parameters to control how
the navigation update will happen.

It is not mandatory for custom location hooks to support this, however the built-in
pushState-powered useLocation hook accepts replace flag to tell the hook to modify the current
history entry instead of adding a new one. It is the same as calling replaceState. Example:

const [location, navigate] = useLocation();

navigate("/jobs"); // `pushState` is used
navigate("/home", { replace: true }); // `replaceState` is used

Customizing the location hook

By default, wouter uses useLocation hook that reacts to pushState and replaceState
navigation and observes the current pathname including the leading slash e.g. /app/users.

If you do need a custom history observer, for example, for hash-based routing, you can
implement your own hook and
customize it in a <Router /> component.

As an exercise, let’s implement a simple location hook that listens to hash changes:

import { useState, useEffect } from "react";
import { Router, Route } from "wouter";
import { useLocationProperty, navigate } from "wouter/use-location";

// returns the current hash location in a normalized form
// (excluding the leading '#' symbol)
const hashLocation = () => window.location.hash.replace(/^#/, "") || "/";

const hashNavigate = (to) => navigate("#" + to);

const useHashLocation = () => {
  const location = useLocationProperty(hashLocation);
  return [location, hashNavigate];
};

const App = () => (
  <Router hook={useHashLocation}>
    <Route path="/about" component={About} />
    ...
  </Router>
);

â–¶ Demo Sandbox: hash-based routing

useParams hook: working with parameters

This hook allows you to access the parameters exposed through matching dynamic segments. Beneath, we simply wrap your components with a context provider allowing you to access this data anywhere within the Route component.

import { Route, useParams } from "wouter";

const User = () => {
  const params = useParams();

  console.log(params.id)
};

<Route path="/user/:id" component={User}> />

useRouter: accessing the router object

If you’re building an advanced integration, for example custom location hook, you might want to get
access to the global router object. The router is a simple object that holds current matcher
function and a custom location hook function.

Normally, router is constructed internally on demand, but it can also be customized via a top-level
Router component (see the section above). The
useRouter hook simply returns a current router object:

import { useRouter } from "wouter";
import useLocation from "wouter/use-location";

const Custom = () => {
  const router = useRouter();

  // router.hook is useLocation by default

  // you can also use router as a mediator object
  // and store arbitrary data on it:
  router.lastTransition = { path: "..." };
};

Component API

<Route path={pattern} />

Route represents a piece of the app that is rendered conditionally based on a pattern. Pattern is
a string, which may contain special characters to describe dynamic segments, see
Matching Dynamic Segments section below for details.

The library provides multiple ways to declare a route’s body:

import { Route } from "wouter";

// simple form
<Route path="/home"><Home /></Route>

// render-prop style
<Route path="/users/:id">
  {params => <UserPage id={params.id} />}
</Route>

// the `params` prop will be passed down to <Orders />
<Route path="/orders/:status" component={Orders} />

<Link href={path} />

Link component renders an <a /> element that, when clicked, performs a navigation. You can
customize the link appearance by providing your own component or a link element as children:

import { Link } from "wouter"

// All of these will produce the same html:
// <a href="/foo" class="active">Hello!</a>

// lazy form: `a` element is constructed around children
<Link href="/foo" className="active">Hello!</Link>

// when using your own component or jsx the `href` prop
// will be passed down to an element
<Link href="/foo"><a className="active">Hello!</a></Link>
<Link href="/foo"><A>Hello!</A></Link>

If you wrap a custom component with Link, wouter won’t install event listeners so make sure the
component handles onClick and href props properly:

import { Link } from "wouter";

const MyButton = (props) => {
  // it is recommended to use <a>'s when possible (they play nicely with SSR and are SEO-friendly),
  // but wouter's Links should work with almost anything, as long as the `onClick` is handled.
  return (
    <div title={props.href}>
      <button onClick={props.onClick}>Home</button>
    </div>
  );
};

// in your app
<Link href="/home">
  <MyButton />
</Link>;

<Switch />

There are cases when you want to have an exclusive routing: to make sure that only one route is
rendered at the time, even if the routes have patterns that overlap. That’s what Switch does: it
only renders the first matching route.

import { Route, Switch } from "wouter";

<Switch>
  <Route path="/orders/all" component={AllOrders} />
  <Route path="/orders/:status" component={Orders} />

  {/* 
     in wouter, any Route with empty path is considered always active. 
     This can be used to achieve "default" route behaviour within Switch. 
     Note: the order matters! See examples below.
  */}
  <Route>This is rendered when nothing above has matched</Route>
</Switch>;

Check out FAQ and Code Recipes section for more advanced use
of Switch.

<Redirect to={path} />

When mounted performs a redirect to a path provided. Uses useLocation hook internally to trigger
the navigation inside of a useEffect block.

If you need more advanced logic for navigation, for example, to trigger the redirect inside of an
event handler, consider using
useLocation hook instead:

import { useLocation } from "wouter";

const [location, setLocation] = useLocation();

fetchOrders().then((orders) => {
  setOrders(orders);
  setLocation("/app/orders");
});

<Router hook={hook} matcher={matchFn} base={basepath} />

Unlike React Router, routes in wouter don’t have to be wrapped in a top-level component. An
internal router object will be constructed on demand, so you can start writing your app without
polluting it with a cascade of top-level providers. There are cases however, when the routing
behaviour needs to be customized.

These cases include hash-based routing, basepath support, custom matcher function etc.

A router is a simple object that holds the routing configuration options. You can always obtain this
object using a useRouter hook. The list of currently
available options:

  • hook: () => [location: string, setLocation: fn] — is a React Hook function that subscribes
    to location changes. It returns a pair of current location string e.g. /app/users and a
    setLocation function for navigation. You can use this hook from any component of your app by
    calling useLocation() hook.

Read more → Customizing the location hook.

  • matcher: (pattern: string, path: string) => [match: boolean, params: object] — a custom
    function used for matching the current location against the user-defined patterns like
    /app/users/:id. Should return a match result and an hash of extracted parameters. It should
    return [false, null] when there is no match.

  • base: string — an optional setting that allows to specify a base path, such as /app. All
    application routes will be relative to that path. Prefixing a route with ~ will make it
    absolute, bypassing the base path.

Matching Dynamic Segments

Just like in React Router, you can make dynamic matches either with Route component or useRoute
hook. useRoute returns a second parameter which is a hash of all dynamic segments matched.
Similarily, the Route component passes these parameters down to its children via a function prop.

import { useRoute } from "wouter";

// /users/alex => [true, { name: "alex "}]
// /anything   => [false, null]
const [match, params] = useRoute("/users/:name");

// or with Route component
<Route path="/users/:name">
  {(params) => {
    /* { name: "alex" } */
  }}
</Route>;

wouter implements a limited subset of
path-to-regexp package used by React Router or
Express, and it supports the following patterns:

  • Named dynamic segments: /users/:foo.
  • Dynamic segments with modifiers: /foo/:bar*, /foo/:baz? or /foo/:bar+.

The library was designed to be as small as possible, so most of the additional matching features
were left out (see this issue for more info).

Using a path-to-regexp-based matcher

The <Router /> component accepts an optional prop called matcher which allows to customize how a
path is matched against the pattern. By default, a built-in matcher function is used, which
implements basic functionality such as wildcard parameters (see above).

However, if you do need to have more advanced functionality, you can specify your own matcher which
should look like:

/*
 * accepts a pattern and a path as strings, should return a pair of values:
 * [success, params]
 */

// returns [false, null] when there is no match
matcher("/users", "/");

// [true, { id: "101" }]
matcher("/users/:id", "/users/101");

Most of the packages for parsing route patterns work with regular expressions (see
path-to-regexp or a super-tiny alternative
regexparam), so to make it easier for you wouter provides
a factory function for transforming
a regexp-based pattern builder into a matcher. It also makes sure that the expensive transform
operation isn’t called on each render by utilizing a simple cache.

import { Router } from "wouter";

import makeCachedMatcher from "wouter/matcher";

/*
 * This function specifies how strings like /app/:users/:items* are
 * transformed into regular expressions.
 *
 * Note: it is just a wrapper around `pathToRegexp`, which uses a
 * slightly different convention of returning the array of keys.
 *
 * @param {string} path — a path like "/:foo/:bar"
 * @return {{ keys: [], regexp: RegExp }}
 */
const convertPathToRegexp = (path) => {
  let keys = [];

  // we use original pathToRegexp package here with keys
  const regexp = pathToRegexp(path, keys, { strict: true });
  return { keys, regexp };
};

const customMatcher = makeCachedMatcher(convertPathToRegexp);

function App() {
  return (
    <Router matcher={customMatcher}>
      {/* at the moment wouter doesn't support inline regexps, but path-to-regexp does! */}
      <Route path="/(resumes|cover-letters)/:id" component={Dashboard} />
    </Router>
  );
}

â–¶ Demo Sandbox

FAQ and Code Recipes

I deploy my app to the subfolder. Can I specify a base path?

You can! Wrap your app with <Router base="/app" /> component and that should do the trick:

import { Router, Route, Link } from "wouter";

const App = () => (
  <Router base="/app">
    {/* the link's href attribute will be "/app/users" */}
    <Link href="/users">Users</Link>

    <Route path="/users">The current path is /app/users!</Route>
  </Router>
);

Note: the base path feature is only supported by the default browser History API location hook
(the one exported from "wouter/use-location"). If you’re implementing your own location hook,
you’ll need to add base path support yourself.

How do I make a default route?

One of the common patterns in application routing is having a default route that will be shown as a
fallback, in case no other route matches (for example, if you need to render 404 message). In
wouter this can easily be done as a combination of <Switch /> component and a default route:

import { Switch, Route } from "wouter";

<Switch>
  <Route path="/about">...</Route>
  <Route>404, Not Found!</Route>
</Switch>;

Note: the order of switch children matters, default route should always come last. If you want to
have access to the matched segment of the path you can use :param*:

<Switch>
  <Route path="/users">...</Route>

  {/* will match anything that starts with /users/, e.g. /users/foo, /users/1/edit etc. */}
  <Route path="/users/:rest*">...</Route>

  {/* will match everything else */}
  <Route path="/:rest*">{(params) => `404, Sorry the page ${params.rest} does not exist!`}</Route>
</Switch>

â–¶ Demo Sandbox

How do I make a link active for the current route?

There are cases when you need to highlight an active link, for example, in the navigation bar. While
this functionality isn’t provided out-of-the-box, you can easily write your own <Link /> wrapper
and detect if the path is active by using the useRoute hook. The useRoute(pattern) hook returns
a pair of [match, params], where match is a boolean value that tells if the pattern matches
current location:

const [isActive] = useRoute(props.href);

return (
  <Link {...props}>
    <a className={isActive ? "active" : ""}>{props.children}</a>
  </Link>
);

â–¶ Demo Sandbox

Are strict routes supported?

If a trailing slash is important for your app’s routing, you could specify a custom matcher that
implements the strict option support.

import makeMatcher from "wouter/matcher";
import { pathToRegexp } from "path-to-regexp";

const customMatcher = makeMatcher((path) => {
  let keys = [];
  const regexp = pathToRegexp(path, keys, { strict: true });
  return { keys, regexp };
});

const App = () => (
  <Router matcher={customMatcher}>
    <Route path="/foo">...</Route>
    <Route path="/foo/">...</Route>
  </Router>
);

â–¶ Demo Sandbox

Are relative routes and links supported?

Since v2.9.0, you can easily implement route nesting by using base
and parent props together passed to the Router component. Declaring base path means that all child routes, links and hooks will
use current location relative to the base. For example:

<Router base="/app">
  <Route path="/users">Users</Route> {/* Location here is "/users", not "/app/users"! */}
</Router>

Additionally, to create multiple nesting contexts, base paths can be inherited from the parent router. This
isn’t done be default for performance reasons, so you’ll have to provide it manually via the parent prop.

const UserRoutes = () => {
  const router = useRouter();

  // takes current base path and appends "/users" to it 
  return (
    <Router base="/users" parent={router}>
      <Route path="/all">All users</Route>
      <Route path="/:id">User profile</Route>
    </Router>
  );
};

const App = () => {
  return (
    <Router base="/app">
      {/* matches "/app/home" */}
      <Route path="/home" /> 

      {/* matches "/app/users/all" and "/app/users/:id" */}
      <UserRoutes />
    </Router>
  )
}

â–¶ Demo Sandbox

Is it possible to match an array of paths?

While wouter doesn’t currently support multipath routes, you can achieve that in your app by
specifying a custom matcher function:

import makeMatcher from "wouter/matcher";

const defaultMatcher = makeMatcher();

/*
 * A custom routing matcher function that supports multipath routes
 */
const multipathMatcher = (patterns, path) => {
  for (let pattern of [patterns].flat()) {
    const [match, params] = defaultMatcher(pattern, path);
    if (match) return [match, params];
  }

  return [false, null];
};

const App = () => (
  <Router matcher={multipathMatcher}>
    <Route path={["/app", "/home"]}>...</Route>
  </Router>
);

â–¶ Demo Sandbox

Can I initiate navigation from outside a component?

Yes, the navigate function is exposed from the "wouter/use-location" module:

import { navigate } from "wouter/use-location" 

navigate("/", { replace: true });

It’s the same function that is used internally.

Can I use wouter in my TypeScript project?

Yes! Although the project isn’t written in TypeScript, the type definition files are bundled with
the package.

Preact support?

Preact exports are available through a separate package named wouter-preact (or within the
wouter/preact namespace, however this method isn’t recommended as it requires React as a peer
dependency):

- import { useRoute, Route, Switch } from "wouter";
+ import { useRoute, Route, Switch } from "wouter-preact";

You might need to ensure you have the latest version of
Preact X with support for hooks.

â–¶ Demo Sandbox

Server-side Rendering support (SSR)?

In order to render your app on the server, you’ll need to wrap your app with top-level Router and
specify ssrPath prop (usually, derived from current request).

import { renderToString } from "react-dom/server";
import { Router } from "wouter";

const handleRequest = (req, res) => {
  // top-level Router is mandatory in SSR mode
  const prerendered = renderToString(
    <Router ssrPath={req.path}>
      <App />
    </Router>
  );

  // respond with prerendered html
};

On the client, the static markup must be hydrated in order for your app to become interactive. Note
that to avoid having hydration warnings, the JSX rendered on the client must match the one used by
the server, so the Router component must be present.

import { hydrateRoot } from "react-dom/client";

const root = hydrateRoot(
  domNode,
  // during hydration `ssrPath` is set to `location.pathname`
  <Router>
    <App />
  </Router>
);

1KB is too much, I can’t afford it!

We’ve got some great news for you! If you’re a minimalist bundle-size nomad and you need a damn
simple routing in your app, you can just use the
useLocation hook which is only 400 bytes gzipped
and manually match the current location with it:

import useLocation from "wouter/use-location";

const UsersRoute = () => {
  const [location] = useLocation();

  if (location !== "/users") return null;

  // render the route
};

Wouter’s motto is “Minimalist-friendly”.

Acknowledgements

Wouter illustrations and logos were made by Katya Simacheva and
Katya Vakulenko.