wouter

repository·v3·Indexed 27 days ago

https://github.com/molefrog/wouter

A minimalistic, dependency-free router for modern React and Preact applications. It provides a tiny footprint (approx. 2.1 KB gzipped) and offers three API styles: a Component API (Route, Link, Switch, Redirect, Router), Routing Hooks (useRoute, useLocation, useParams, useSearch, useSearchParams, useRouter), and Location Hooks (useBrowserLocation, useHashLocation, memoryLocation).

Tokens
11.3K
Snippets
32
Records
79
Agent score
90%

What's inside wouter

  1. Specify a base path for your app

    v3

    If your application is deployed to a subfolder, wrap your application with the <Router /> component and provide the base prop. This scopes all <Link /> href attributes and useLocation() return values to the base path. Nested routers will inherit and stack base paths.

    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>
    );
  2. Configure Server-Side Rendering (SSR)

    v3

    For SSR, wrap your app in a top-level <Router /> and provide the ssrPath (the request path) and optionally ssrSearch (the query string). On the client, use hydrateRoot and ensure the <Router /> is present to match the server-rendered markup.

    import { renderToString } from "react-dom/server";
    import { Router } from "wouter";
    
    const handleRequest = (req, res) => {
      const ssrContext = {};
      const prerendered = renderToString(
        <Router ssrPath={req.path} ssrSearch={req.search} ssrContext={ssrContext}>
          <App />
        </Router>
      );
    
      if (ssrContext.redirectTo) {
        res.redirect(ssrContext.redirectTo);
      } else {
        // respond with prerendered html
      }
    };
  3. Use Opt-in View Transitions

    v3

    You can control exactly when a view transition occurs by using the transition option. This can be done via the <Link> component or programmatically through location hooks.

    To use this, your aroundNav handler must check for the transition property within the navigation options.

    // Using the Link component
    <Link to="/" transition>
      Home
    </Link>;
    
    // Using the useLocation hook
    const [location, navigate] = useLocation();
    navigate("/", { transition: true });
    
    // Implementation of aroundNav to support opt-in
    function aroundNav(navigate, to, options) {
      if (!document.startViewTransition) {
        navigate(to, options);
        return;
      }
    
      if (options?.transition) {
        document.startViewTransition(() => {
          flushSync(() => {
            navigate(to, options);
          });
        });
      } else {
        navigate(to, options);
      }
    }
  4. Use nested routes and relative links

    v3

    Adding the nest prop to a <Route /> creates a nesting context. Inside a nested route, the location is scoped to that route. This allows <Link /> components to use relative paths.

    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>
    );
  5. Integrate with the View Transitions API

    v3

    To use the View Transitions API, use the aroundNav prop on the <Router />. This allows you to wrap the navigate call within document.startViewTransition. You can trigger transitions selectively by passing { transition: true } in the navigation options.

    import { flushSync } from "react-dom";
    import { Router, type AroundNavHandler } from "wouter";
    
    const aroundNav: AroundNavHandler = (navigate, to, options) => {
      if (!document.startViewTransition) {
        navigate(to, options);
        return;
      }
    
      document.startViewTransition(() => {
        flushSync(() => {
          navigate(to, options);
        });
      });
    };
    
    const App = () => (
      <Router aroundNav={aroundNav}>
        {/* Your routes here */}
      </Router>
    );
  6. Enable View Transitions in Wouter

    v3

    To enable the browser's View Transitions API in wouter, you must provide an aroundNav handler to the <Router /> component. Because startViewTransition requires synchronous DOM updates, you must use flushSync from react-dom inside the transition callback to ensure the navigation occurs synchronously.

    Note: This implementation requires react-dom to be installed in your project.

    import { flushSync } from "react-dom";
    
    function aroundNav(navigate, ...navArgs) {
      // Feature detection for older browsers
      if (!document.startViewTransition) {
        navigate(...navArgs);
        return;
      }
    
      document.startViewTransition(() => {
        flushSync(() => {
          navigate(...navArgs);
        });
      });
    }
    
    <Router aroundNav={aroundNav}>
      <App />
    </Router>;
  7. Create an active link for the current route

    v3

    The <Link /> component accepts a function for the className prop. This function receives an active boolean which is true if the link matches the current route exactly. For more complex logic (like style or aria-current), use the useRoute hook to detect if a path is active.

    // Using className function for exact matches
    <Link className={(active) => (active ? "active" : "")}>Nav link</Link>
    
    // Using useRoute for custom logic
    const [isActive] = useRoute(props.href);
    
    return (
      <Link {...props} asChild>
        <a style={isActive ? { color: "red" } : {}}>{props.children}</a>
      </Link>
    );
  8. Create a default (fallback) route

    v3

    To implement a fallback route (like a 404 page), use the <Switch /> component and place a <Route /> without a path prop as the last child. The order of children in <Switch /> matters; the default route must always be last.

    import { Switch, Route } from "wouter";
    
    <Switch>
      <Route path="/about">...</Route>
      <Route>404, Not Found!</Route>
    </Switch>;
  9. Test routes using memoryLocation

    v3

    When testing, use memoryLocation from wouter/memory-location to provide a controlled location fixture. This allows you to render specific routes without a real browser environment. You can pass the returned hook and searchHook to the <Router />.

    import { render } from "@testing-library/react";
    import { memoryLocation } from "wouter/memory-location";
    
    it("renders a user page", () => {
      const { hook, searchHook } = memoryLocation({ path: "/user/2", static: true });
    
      const { container } = render(
        <Router hook={hook} searchHook={searchHook}>
          <Route path="/user/:id">{(params) => <>User ID: {params.id}</>}</Route>
        </Router>
      );
    
      expect(container.innerHTML).toBe("User ID: 2");
    });
  10. Quickstart with wouter components

    v3

    Wouter provides a component-based API similar to React Router. You can use Link, Route, and Switch to build a basic routing structure.

    Note that Switch performs exclusive routing: it only renders the first route that matches the current path. To create a default (404) route, place a Route without a path prop as the last child of the Switch.

    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>
    
          {/* Default route in a switch */}
          <Route>404: No such page!</Route>
        </Switch>
      </>
    );
    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>
      </>
    );
  11. Implement strict routes with a custom parser

    v3

    By default, wouter handles routing with a specific pattern. If you need strict routing (e.g., treating /foo and /foo/ differently), provide a custom parser function to the <Router />. The parser must take a pattern string and return an object containing a pattern (RegExp) and keys (array of parsed key names).

    import { pathToRegexp } from "path-to-regexp";
    
    const strictParser = (path, loose) => {
      const keys = [];
      const pattern = pathToRegexp(path, keys, { strict: true, end: !loose });
    
      return {
        pattern,
        keys: keys.map((k) => k.name),
      };
    };
    
    const App = () => (
      <Router parser={strictParser}>
        <Route path="/foo">...</Route>
        <Route path="/foo/">...</Route>
      </Router>
    );