Top Related Projects
Declarative routing for React
🧭 Declarative, asynchronous routing for React.
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering.
Quick Overview
Wouter is a minimalist, 3kB routing library for React and Preact. It provides a simple and lightweight alternative to more feature-rich routing solutions, focusing on ease of use and performance.
Pros
- Lightweight: Wouter is a very small library, weighing in at only 3kB gzipped, making it a great choice for projects where size is a concern.
- Simple API: The API is straightforward and easy to understand, with a focus on simplicity over complexity.
- Framework-agnostic: Wouter works with both React and Preact, making it a versatile choice for different project requirements.
- No dependencies: Wouter does not rely on any external dependencies, keeping the overall project footprint small.
Cons
- Limited features: Compared to more feature-rich routing libraries, Wouter may lack some advanced functionality, such as nested routes, dynamic segments, or server-side rendering support.
- Lack of community: Wouter has a smaller community compared to more popular routing solutions, which may mean fewer resources and support available.
- Opinionated approach: Wouter takes an opinionated approach to routing, which may not align with the preferences of all developers.
- Potential performance issues: While Wouter is generally fast, in some cases, the library's simplicity may result in performance issues, especially for complex routing scenarios.
Code Examples
Here are a few examples of how to use Wouter in a React application:
import { useRoute, Link } from 'wouter';
function App() {
const [isActive, params] = useRoute('/users/:id');
return (
<div>
<nav>
<Link href="/users/123">User 123</Link>
<Link href="/about">About</Link>
</nav>
{isActive && (
<div>
<h1>User {params.id}</h1>
<p>This is the user page.</p>
</div>
)}
</div>
);
}
This example demonstrates the use of the useRoute
hook to handle route matching and the Link
component to create navigation links.
import { Router, Route } from 'wouter';
function App() {
return (
<Router>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={UserPage} />
</Router>
);
}
function Home() {
return <h1>Welcome to the homepage!</h1>;
}
function About() {
return <h1>This is the about page.</h1>;
}
function UserPage({ params }) {
return <h1>User {params.id}</h1>;
}
This example demonstrates the use of the Router
and Route
components to define the application's routing structure.
import { useLocation, navigate } from 'wouter';
function App() {
const [location, setLocation] = useLocation();
const handleClick = () => {
navigate('/about');
};
return (
<div>
<p>Current location: {location}</p>
<button onClick={handleClick}>Go to About</button>
</div>
);
}
This example demonstrates the use of the useLocation
hook to access the current location and the navigate
function to programmatically change the URL.
Getting Started
To get started with Wouter, you can install the library using npm or yarn:
npm install wouter
or
yarn add wouter
Once installed, you can import the necessary components and hooks from the library and start using them in your React application. The basic setup would look like this:
import React from 'react';
import { Router, Route, Link } from 'wouter';
function App() {
return (
<Router>
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/users/123">User 123</Link>
</nav>
<Route path="/" component={
Competitor Comparisons
Declarative routing for React
Pros of React Router
- More comprehensive feature set, including nested routing and route-based code splitting
- Larger community and ecosystem, with extensive documentation and third-party integrations
- Supports both hash-based and browser history routing out of the box
Cons of React Router
- Larger bundle size, which may impact initial load times for smaller applications
- Steeper learning curve due to its more complex API and additional concepts
- More opinionated, potentially leading to less flexibility in certain use cases
Code Comparison
React Router:
import { BrowserRouter, Route, Switch } from 'react-router-dom';
<BrowserRouter>
<Switch>
<Route path="/about" component={About} />
<Route path="/" exact component={Home} />
</Switch>
</BrowserRouter>
Wouter:
import { Router, Route } from 'wouter';
<Router>
<Route path="/about" component={About} />
<Route path="/" component={Home} />
</Router>
React Router offers a more verbose syntax with additional components like Switch
, while Wouter provides a simpler, more lightweight approach to routing. React Router's exact
prop is not needed in Wouter, as it uses exact matching by default. Both libraries support similar basic routing functionality, but React Router offers more advanced features at the cost of increased complexity.
Pros of reach/router
- More comprehensive routing solution with built-in accessibility features
- Supports nested routes and relative navigation
- Offers a more React-centric API with hooks and components
Cons of reach/router
- Larger bundle size compared to wouter
- Steeper learning curve for developers new to React routing
- Less frequently updated and maintained
Code Comparison
reach/router:
import { Router, Link } from "@reach/router"
const App = () => (
<Router>
<Home path="/" />
<Dashboard path="dashboard" />
</Router>
)
wouter:
import { Router, Link, Route } from "wouter"
const App = () => (
<Router>
<Route path="/" component={Home} />
<Route path="/dashboard" component={Dashboard} />
</Router>
)
Summary
reach/router offers a more feature-rich routing solution with built-in accessibility and nested routing support. However, it comes with a larger bundle size and a steeper learning curve. wouter, on the other hand, provides a lightweight and simple routing solution that may be more suitable for smaller projects or developers who prefer a minimalistic approach. The choice between the two depends on the specific needs of your project and your familiarity with React routing concepts.
🧭 Declarative, asynchronous routing for React.
Pros of navi
- More comprehensive routing solution with built-in support for nested routes and route parameters
- Offers a declarative API for defining routes, making it easier to manage complex routing structures
- Provides better TypeScript support out of the box
Cons of navi
- Larger bundle size compared to wouter, which may impact performance for smaller applications
- Steeper learning curve due to its more feature-rich API
- Less suitable for simple routing scenarios where a lightweight solution is preferred
Code Comparison
navi:
<Router routes={routes}>
<View />
</Router>
wouter:
<Switch>
<Route path="/users/:id" component={User} />
<Route path="/about" component={About} />
</Switch>
Both libraries offer a declarative approach to routing, but navi's API is more focused on defining routes as data structures, while wouter follows a more traditional React component-based approach. navi's routing configuration is typically done separately from the component tree, whereas wouter's routes are defined directly within the JSX.
navi is better suited for larger applications with complex routing needs, while wouter shines in simpler scenarios where a lightweight solution is preferred. The choice between the two depends on the specific requirements of your project and the level of routing complexity you need to handle.
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering.
Pros of TanStack Router
- More comprehensive routing solution with advanced features like nested routing and route matching
- Built-in TypeScript support for better type safety and developer experience
- Supports multiple frameworks (React, Vue, Solid) and server-side rendering
Cons of TanStack Router
- Steeper learning curve due to its more complex API and concepts
- Larger bundle size compared to Wouter's lightweight approach
- Requires more setup and configuration for basic use cases
Code Comparison
Wouter:
import { Route, Switch } from "wouter";
<Switch>
<Route path="/users/:id" component={UserProfile} />
<Route path="/about" component={About} />
</Switch>
TanStack Router:
import { Router, Route, RootRoute } from '@tanstack/react-router'
const rootRoute = new RootRoute()
const usersRoute = new Route({
getParentRoute: () => rootRoute,
path: 'users/$userId',
component: UserProfile,
})
const aboutRoute = new Route({
getParentRoute: () => rootRoute,
path: 'about',
component: About,
})
Both routers offer declarative routing for React applications, but TanStack Router provides a more powerful and flexible API at the cost of simplicity. Wouter focuses on minimalism and ease of use, while TanStack Router offers a feature-rich solution for complex routing needs across multiple frameworks.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
A router you wanted so bad in your project!
Features
â ï¸ These docs are for wouter v3 only. Please find the documentation for wouter@2.12.0 here
- Minimum dependencies, only 2.1 KB gzipped vs 18.7KB React Router.
- Supports both React and Preact! Read "Preact support" section for more details.
- No top-level
<Router />
component, it is fully optional. - Mimics React Router's best practices by providing
familiar
Route
,Link
,Switch
andRedirect
components. - Has hook-based API for more granular control over routing (like animations):
useLocation
,useRoute
anduseRouter
.
developers :sparkling_heart: 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.
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
-
- I deploy my app to the subfolder. Can I specify a base path?
- How do I make a default route?
- How do I make a link active for the current route?
- Are strict routes supported?
- Are relative routes and links supported?
- Can I initiate navigation from outside a component?
- Can I use wouter in my TypeScript project?
- How can add animated route transitions?
- Preact support?
- Server-side Rendering support (SSR)?
- How do I configure the router to render a specific route in tests?
- 1KB is too much, I can't afford it!
Getting Started
First, add wouter to your project.
npm i wouter
Or, if you're using Preact the use the following command npm i wouter-preact
.
Check out this simple demo app below. It doesn't cover hooks and other features such as nested routing, but it's a good starting point for those who are migrating from React Router.
import { Link, Route, Switch } from "wouter";
const App = () => (
<>
<Link href="/users/1">Profile</Link>
<Route path="/about">About Us</Route>
{/*
Routes below are matched exclusively -
the first matched route gets rendered
*/}
<Switch>
<Route path="/inbox" component={InboxPage} />
<Route path="/users/:name">
{(params) => <>Hello, {params.name}!</>}
</Route>
{/* Default route in a switch */}
<Route>404: No such page!</Route>
</Switch>
</>
);
Browser Support
This library is designed for ES2020+ compatibility. If you need to support older browsers, make sure that you transpile node_modules
. Additionally, the minimum supported TypeScript version is 4.1 in order to support route parameter inference.
Wouter API
Wouter comes with three kinds of APIs: low-level standalone location hooks, hooks for routing and pattern matching and more traditional component-based API similar to React Router's one.
You are free to choose whatever works for you: use location hooks when you want to keep your app as small as possible and don't need pattern matching; use routing hooks when 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, server-side rendering etc.
The list of methods available
Location Hooks
These can be used separately from the main module and have an interface similar to useState
. These hooks don't support nesting, base path, route matching.
import { useBrowserLocation } from "wouter/use-browser-location"
â allows to manipulate current location in the browser's address bar, a tiny wrapper around the History API.import { useHashLocation } from "wouter/use-hash-location"
â similarly, gets location from the hash part of the address, i.e. the string after a#
.import { memoryLocation } from "wouter/memory-location"
â an in-memory location hook with history support, external navigation and immutable mode for testing. Note the module name because it is a high-order hook. See how memory location can be used in testing.
Routing Hooks
Import from wouter
module.
useRoute
â shows whether or not current page matches the pattern provided.useLocation
â allows to manipulate current router's location, by default subscribes to browser location. Note: this isn't the same asuseBrowserLocation
, read below.useParams
â returns an object with parameters matched from the closest route.useSearch
â returns a search string â everything that goes after the?
.useRouter
â returns a global router object that holds the configuration. Only use it if you want to customize the routing.
Components
Import from wouter
module.
<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
: route matching and parameters
Checks if the current location matches the pattern provided and returns an object with parameters. This is powered by a wonderful regexparam
library, so all its pattern syntax is fully supported.
You can use useRoute
to perform manual routing or implement custom logic, such as route transitions, etc.
import { useRoute } from "wouter";
const Users = () => {
// `match` is a boolean
const [match, params] = useRoute("/users/:name");
if (match) {
return <>Hello, {params.name}!</>;
} else {
return null;
}
};
A quick cheatsheet of what types of segments are supported:
useRoute("/app/:page");
useRoute("/app/:page/:section");
// optional parameter, matches "/en/home" and "/home"
useRoute("/:locale?/home");
// suffixes
useRoute("/movies/:title.(mp4|mov)");
// wildcards, matches "/app", "/app-1", "/app/home"
useRoute("/app*");
// optional wildcards, matches "/orders", "/orders/"
// and "/orders/completed/list"
useRoute("/orders/*?");
// regex for matching complex patterns,
// matches "/hello:123"
useRoute(/^[/]([a-z]+):([0-9]+)[/]?$/);
// and with named capture groups
useRoute(/^[/](?<word>[a-z]+):(?<num>[0-9]+)[/]?$/);
The second item in the pair params
is an object with parameters or null if there was no match. For wildcard segments the parameter name is "*"
:
// wildcards, matches "/app", "/app-1", "/app/home"
const [match, params] = useRoute("/app*");
if (match) {
// "/home" for "/app/home"
const page = params["*"];
}
useLocation
: working with the history
To get the current path and navigate between pages, call the useLocation
hook. Similarly to useState
, it returns a value and a setter: the component will re-render when the location changes and by calling navigate
you can update this value and perform navigation.
By default, it uses useBrowserLocation
under the hood, though you can configure this in a top-level Router
component (for example, if you decide at some point to switch to a hash-based routing). useLocation
will also return scoped path when used within nested routes or with base path setting.
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 internally call the useLocation
hook.
Additional navigation parameters
The setter method of useLocation
can also accept an optional object with parameters to control how
the navigation update will happen.
When browser location is used (default), 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
.
const [location, navigate] = useLocation();
navigate("/jobs"); // `pushState` is used
navigate("/home", { replace: true }); // `replaceState` is used
Additionally, you can provide a state
option to update history.state
while navigating:
navigate("/home", { state: { modal: "promo" } });
history.state; // { modal: "promo" }
Customizing the location hook
By default, wouter uses useLocation
hook that reacts to pushState
and replaceState
navigation via useBrowserLocation
.
To customize this, wrap your app in a Router
component:
import { Router, Route } from "wouter";
import { useHashLocation } from "wouter/use-hash-location";
const App = () => (
<Router hook={useHashLocation}>
<Route path="/about" component={About} />
...
</Router>
);
Because these hooks have return values similar to useState
, it is easy and fun to build your own location hooks: useCrossTabLocation
, useLocalStorage
, useMicroFrontendLocation
and whatever routing logic you want to support in the app. Give it a try!
useParams
: extracting matched parameters
This hook allows you to access the parameters exposed through matching dynamic segments. Internally, we simply wrap your components in a context provider allowing you to access this data anywhere within the Route
component.
This allows you to avoid "prop drilling" when dealing with deeply nested components within the route. Note: useParams
will only extract parameters from the closest parent route.
import { Route, useParams } from "wouter";
const User = () => {
const params = useParams();
params.id; // "1"
// alternatively, use the index to access the prop
params[0]; // "1"
};
<Route path="/user/:id" component={User}> />
It is the same for regex paths. Capture groups can be accessed by their index, or if there is a named capture group, that can be used instead.
import { Route, useParams } from "wouter";
const User = () => {
const params = useParams();
params.id; // "1"
params[0]; // "1"
};
<Route path={/^[/]user[/](?<id>[0-9]+)[/]?$/} component={User}> />
useSearch
: query strings
Use this hook to get the current search (query) string value. It will cause your component to re-render only when the string itself and not the full location updates. The search string returned does not contain a ?
character.
import { useSearch } from "wouter";
// returns "tab=settings&id=1"
// the hook for extracting search parameters is coming soon!
const searchString = useSearch();
For the SSR, use ssrSearch
prop passed to the router.
<Router ssrSearch={request.search}>{/* SSR! */}</Router>
Refer to Server-Side Rendering for more info on rendering and hydration.
useRouter
: accessing the router object
If you're building advanced integration, for example custom location hook, you might want to get
access to the global router object. Router is a simple object that holds routing options that you configure in the Router
component.
import { useRouter } from "wouter";
const Custom = () => {
const router = useRouter();
router.hook; // `useBrowserLocation` by default
router.base; // "/app"
};
const App = () => (
<Router base="/app">
<Custom />
</Router>
);
Component API
<Route path={pattern} />
Route
represents a piece of the app that is rendered conditionally based on a pattern path
. Pattern has the same syntax as the argument you pass to useRoute
.
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} />
A route with no path is considered to always match, and it is the same as <Route path="*" />
. When developing your app, use this trick to peek at the route's content without navigation.
-<Route path="/some/page">
+<Route>
{/* Strip out the `path` to make this visible */}
</Route>
Route Nesting
Nesting is a core feature of wouter and can be enabled on a route via the nest
prop. When this prop is present, the route matches everything that starts with a given pattern and it creates a nested routing context. All child routes will receive location relative to that pattern.
Let's take a look at this example:
<Route path="/app" nest>
<Route path="/users/:id" nest>
<Route path="/orders" />
</Route>
</Route>
-
This first route will be active for all paths that start with
/app
, this is equivalent to having a base path in your app. -
The second one uses dynamic pattern to match paths like
/app/user/1
,/app/user/1/anything
and so on. -
Finally, the inner-most route will only work for paths that look like
/app/users/1/orders
. The match is strict, since that route does not have anest
prop and it works as usual.
If you call useLocation()
inside the last route, it will return /orders
and not /app/users/1/orders
. This creates a nice isolation and it makes it easier to make changes to parent route without worrying that the rest of the app will stop working. If you need to navigate to a top-level page however, you can use a prefix ~
to refer to an absolute path:
<Route path="/payments" nest>
<Route path="/all">
<Link to="~/home">Back to Home</Link>
</Route>
</Route>
Note: The nest
prop does not alter the regex passed into regex paths.
Instead, the nest
prop will only determine if nested routes will match against the rest of path or the same path.
To make a strict path regex, use a regex pattern like /^[/](your pattern)[/]?$/
(this matches an optional end slash and the end of the string).
To make a nestable regex, use a regex pattern like /^[/](your pattern)(?=$|[/])/
(this matches either the end of the string or a slash for future segments).
<Link href={path} />
Link component renders an <a />
element that, when clicked, performs a navigation.
import { Link } from "wouter"
<Link href="/">Home</Link>
// `to` is an alias for `href`
<Link to="/">Home</Link>
// all standard `a` props are proxied
<Link href="/" className="link" aria-label="Go to homepage">Home</Link>
// all location hook options are supported
<Link href="/" replace state={{ animate: true }} />
Link will always wrap its children in an <a />
tag, unless asChild
prop is provided. Use this when you need to have a custom component that renders an <a />
under the hood.
// use this instead
<Link to="/" asChild>
<UIKitLink />
</Link>
// Remember, `UIKitLink` must implement an `onClick` handler
// in order for navigation to work!
When you pass a function as a className
prop, it will be called with a boolean value indicating whether the link is active for the current route. You can use this to style active links (e.g. for links in navigation menu)
<Link className={(active) => (active ? "active" : "")}>Nav</Link>
Read more about active links here.
<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>;
When no route in switch matches, the last empty Route
will be used as a fallback. See FAQ and Code Recipes section to read about default routes.
<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.
Redirect
can also accept props for customizing how navigation will be performed, for example for setting history state when navigating. These options are specific to the currently used location hook.
<Redirect to="/" />
// arbitrary state object
<Redirect to="/" state={{ modal: true }} />
// use `replaceState`
<Redirect to="/" replace />
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} parser={fn} base={basepath} hrefs={fn} />
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.
import { useHashLocation } from "wouter/use-hash-location";
<Router hook={useHashLocation} base="/app">
{/* Your app goes here */}
</Router>;
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 currentlocation
string e.g./app/users
and asetLocation
function for navigation. You can use this hook from any component of your app by callinguseLocation()
hook. See Customizing the location hook. -
searchHook: () => [search: string, setSearch: fn]
â similar tohook
, but for obtaining the current search string. -
base: string
â an optional setting that allows to specify a base path, such as/app
. All application routes will be relative to that path. To navigate out to an absolute path, prefix your path with an~
. See the FAQ. -
parser: (path: string, loose?: boolean) => { pattern, keys }
â a pattern parsing function. Produces a RegExp for matching the current location against the user-defined patterns like/app/users/:id
. Has the same interface as theparse
function fromregexparam
. See this example that demonstrates custom parser feature. -
ssrPath: string
andssrSearch: string
use these when rendering your app on the server. -
hrefs: (href: boolean) => string
â a function for transforminghref
attribute of an<a />
element rendered byLink
. It is used to support hash-based routing. By default,href
attribute is the same as thehref
orto
prop of aLink
. A location hook can also define ahook.hrefs
property, in this case thehref
will be inferred.
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>
);
Calling useLocation()
within a route in an app with base path will return a path scoped to the base. Meaning that when base is "/app"
and pathname is "/app/users"
the returned string is "/users"
. Accordingly, calling navigate
will automatically append the base to the path argument for you.
When you have multiple nested routers, base paths are inherited and stack up.
<Router base="/app">
<Router base="/cms">
<Route path="/users">Path is /app/cms/users!</Route>
</Router>
</Router>
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 wildcard parameters:
<Switch>
<Route path="/users">...</Route>
{/* will match anything that starts with /users/, e.g. /users/foo, /users/1/edit etc. */}
<Route path="/users/*">...</Route>
{/* will match everything else */}
<Route path="*">
{(params) => `404, Sorry the page ${params["*"]} does not exist!`}
</Route>
</Switch>
How do I make a link active for the current route?
Instead of a regular className
string, provide a function to use custom class when this link matches the current route. Note that it will always perform an exact match (i.e. /users
will not be active for /users/1
).
<Link className={(active) => (active ? "active" : "")}>Nav link</Link>
If you need to control other props, such as aria-current
or style
, you can write your own <Link />
wrapper
and detect if the path is active by using the useRoute
hook.
const [isActive] = useRoute(props.href);
return (
<Link {...props} asChild>
<a style={isActive ? { color: "red" } : {}}>{props.children}</a>
</Link>
);
Are strict routes supported?
If a trailing slash is important for your app's routing, you could specify a custom parser. Parser is a method that takes a pattern string and returns a RegExp and an array of parsed key. It uses the signature of a parse
function from regexparam
.
Let's write a custom parser based on a popular path-to-regexp
package that does support strict routes option.
import { pathToRegexp } from "path-to-regexp";
/**
* Custom parser based on `pathToRegexp` with strict route option
*/
const strictParser = (path, loose) => {
const keys = [];
const pattern = pathToRegexp(path, keys, { strict: true, end: !loose });
return {
pattern,
// `pathToRegexp` returns some metadata about the keys,
// we want to strip it to just an array of keys
keys: keys.map((k) => k.name),
};
};
const App = () => (
<Router parser={strictParser}>
<Route path="/foo">...</Route>
<Route path="/foo/">...</Route>
</Router>
);
Are relative routes and links supported?
Yes! Any route with nest
prop present creates a nesting context. Keep in mind, that the location inside a nested route will be scoped.
const App = () => (
<Router base="/app">
<Route path="/dashboard" nest>
{/* the href is "/app/dashboard/users" */}
<Link to="/users" />
<Route path="/users">
{/* Here `useLocation()` returns "/users"! */}
</Route>
</Route>
</Router>
);
Can I initiate navigation from outside a component?
Yes, the navigate
function is exposed from the "wouter/use-browser-location"
module:
import { navigate } from "wouter/use-browser-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.
How can add animated route transitions?
Let's take look at how wouter routes can be animated with framer-motion
.
Animating enter transitions is easy, but exit transitions require a bit more work. We'll use the AnimatePresence
component that will keep the page in the DOM until the exit animation is complete.
Unfortunately, AnimatePresence
only animates its direct children, so this won't work:
import { motion, AnimatePresence } from "framer-motion";
export const MyComponent = () => (
<AnimatePresence>
{/* This will not work! `motion.div` is not a direct child */}
<Route path="/">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
</Route>
</AnimatePresence>
);
The workaround is to match this route manually with useRoute
:
export const MyComponent = ({ isVisible }) => {
const [isMatch] = useRoute("/");
return (
<AnimatePresence>
{isMatch && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
);
};
More complex examples involve using useRoutes
hook (similar to how React Router does it), but wouter does not ship it out-of-the-box. Please refer to this issue for the workaround.
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.
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). Optionally, Router
accepts ssrSearch
parameter if need to have access to a search string on a server.
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} ssrSearch={req.search}>
<App />
</Router>
);
// respond with prerendered html
};
Tip: wouter can pre-fill ssrSearch
, if ssrPath
contains the ?
character. So these are equivalent:
<Router ssrPath="/goods?sort=asc" />;
// is the same as
<Router ssrPath="/goods" ssrSearch="sort=asc" />;
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`,
// `ssrSearch` set to `location.search` accordingly
// so there is no need to explicitly specify them
<Router>
<App />
</Router>
);
How do I configure the router to render a specific route in tests?
Testing with wouter is no different from testing regular React apps. You often need a way to provide a fixture for the current location to render a specific route. This can be easily done by swapping the normal location hook with memoryLocation
. It is an initializer function that returns a hook that you can then specify in a top-level Router
.
import { render } from "@testing-library/react";
import { memoryLocation } from "wouter/memory-location";
it("renders a user page", () => {
// `static` option makes it immutable
// even if you call `navigate` somewhere in the app location won't change
const { hook } = memoryLocation({ path: "/user/2", static: true });
const { container } = render(
<Router hook={hook}>
<Route path="/user/:id">{(params) => <>User ID: {params.id}</>}</Route>
</Router>
);
expect(container.innerHTML).toBe("User ID: 2");
});
The hook can be configured to record navigation history. Additionally, it comes with a navigate
function for external navigation.
it("performs a redirect", () => {
const { hook, history, navigate } = memoryLocation({
path: "/",
// will store navigation history in `history`
record: true,
});
const { container } = render(
<Router hook={hook}>
<Switch>
<Route path="/">Index</Route>
<Route path="/orders">Orders</Route>
<Route>
<Redirect to="/orders" />
</Route>
</Switch>
</Router>
);
expect(history).toStrictEqual(["/"]);
navigate("/unknown/route");
expect(container.innerHTML).toBe("Orders");
expect(history).toStrictEqual(["/", "/unknown/route", "/orders"]);
});
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 bare location hooks. For example, useBrowserLocation
hook which is only 650 bytes gzipped
and manually match the current location with it:
import { useBrowserLocation } from "wouter/use-browser-location";
const UsersRoute = () => {
const [location] = useBrowserLocation();
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. Thank you to @jeetiss and all the amazing contributors for helping with the development.
Top Related Projects
Declarative routing for React
🧭 Declarative, asynchronous routing for React.
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot