npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@w3cj/ruta

v1.1.2

Published

A tiny type-safe client-side router for hono/jsx

Readme

ruta

A tiny type-safe client-side router for hono/jsx client components.

Zero dependencies, only 2.51 KB with brotli compression.

Inspired by wouter and @tanstack/react-router

Install

pnpm install @w3cj/ruta

Quick Start

Define routes with defineRoutes and register them via module augmentation. All route-aware APIs (Link, navigate, useRoute, useParams, etc.) get typed automatically.

// src/routes.ts
import { defineRoutes, useParams } from "@w3cj/ruta";
import { Home, Post, User } from "./pages";

export const routes = defineRoutes(route => [
  route("/", Home),
  route("/users/:id", User),
  route("/posts/:year/:slug", Post),
]);

Register route types globally at the bottom of the same file:

declare module "@w3cj/ruta" {
  // eslint-disable-next-line ts/consistent-type-definitions
  interface Register {
    routes: typeof routes;
  }
}

When Register is not augmented, all APIs accept plain strings and params are optional.

<Router routes={routes} />

Pass the route definition to Router

import { Router } from "@w3cj/ruta";
import { routes } from "./routes";

const App = () => <Router routes={routes} />;

Page components used in the example

// src/pages.ts
export const Home = () => <h1>Home</h1>;

export const User = () => {
  const { id } = useParams<"/users/:id">();
  return (
    <h1>
      User
      {id}
    </h1>
  );
};

export const Post = () => {
  const { year, slug } = useParams<"/posts/:year/:slug">();
  return (
    <h1>
      Post
      {year}
      {" - "}
      {slug}
    </h1>
  );
};

<Link> with typed params

The to prop gets autocomplete for registered routes. When a route has parameters, the params prop is required.

import { Link } from "@w3cj/ruta";

<Link to="/about" />;
<Link to="/users/:id" params={{ id: "42" }} />;

navigate with typed params

import { navigate } from "@w3cj/ruta";

navigate("/about");
navigate("/users/:id", { params: { id: "42" } });
navigate("/users/:id", { params: { id: "42" }, replace: true });

<Redirect> with typed params

import { Redirect } from "@w3cj/ruta";

<Redirect to="/about" />;
<Redirect to="/users/:id" params={{ id: "42" }} />;

useParams

Pass a route pattern as a generic to get typed params.

import { useParams } from "@w3cj/ruta";

const { id } = useParams<"/users/:id">(); // { id: string }

useRoute

Returns a typed match result.

import { useRoute } from "@w3cj/ruta";

const { matched, params } = useRoute("/posts/:year/:slug");
// params: { year: string; slug: string }

<Route> with typed callbacks

TypeScript infers param types from the path prop when using a render function.

import { Route } from "@w3cj/ruta";

<Route path="/users/:id">
  {params => (
    <h1>
      User
      {params.id}
    </h1>
  )}
</Route>;

History Types

Ruta supports three history types. Browser history is the default — pass a different history to Router to switch modes.

Browser (default)

Uses the browser History API. No configuration needed.

<Router>
  <App />
</Router>;

Hash

Uses hash-based URLs (/#/about). Useful when your server doesn't support rewrites to index.html.

import { createHashHistory } from "@w3cj/ruta";

<Router history={createHashHistory()}>
  <App />
</Router>;

Memory

In-memory routing for testing or non-browser environments.

import { createMemoryHistory } from "@w3cj/ruta";

const mem = createMemoryHistory({ path: "/initial", record: true });
<Router history={mem}>
  <App />
</Router>;
mem.navigate("/next");
console.log(mem.entries); // ["/initial", "/next"]
mem.reset!();

Components

<Route>

Renders when the path matches.

<Route path="/about" component={About} />;
<Route path="/about">
  <h1>About</h1>
</Route>;
<Route path="/users/:id" component={User} />;
// Nested routing
<Route path="/app" nest>
  <Route path="/dashboard" component={Dashboard} />
  <Route path="/settings" component={Settings} />
</Route>;

<Switch>

Renders the first matching route.

<Switch>
  <Route path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="*" component={NotFound} />
</Switch>;

<Link>

Navigates without a full page reload.

<Link href="/about">About</Link>;
<Link href="/login" replace>Log in</Link>;
<Link href="/about" className={isActive => isActive ? "active" : ""}>
  About
</Link>;
<Link href="/about" asChild>
  <button>About</button>
</Link>;

<Redirect>

Navigates immediately on mount.

<Redirect href="/login" />;
<Redirect href="/home" replace />;

<Router>

Provides routing context. Optional — browser history is used by default.

<Router>
  <App />
</Router>;
<Router history={createHashHistory()}>
  <App />
</Router>;

Base Path

Prepend all routes with a base path.

<Router base="/dashboard">
  <App />
</Router>;

Hooks

useLocation

Returns the current path and a navigate function.

const { location, navigate } = useLocation();

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

useRoute

Matches a pattern against the current path.

const { matched, params } = useRoute("/users/:id");

if (matched) {
  console.log(params.id);
}

useParams

Returns route parameters from the nearest <Route>.

const { id } = useParams<"/users/:id">();

useSearch

Returns the search string (without the leading ?).

// URL: /page?sort=name&page=2
const search = useSearch(); // "sort=name&page=2"

useSearchParams

Returns URLSearchParams and a setter.

const { params, setParams } = useSearchParams();

const sort = params.get("sort");

setParams(new URLSearchParams({ sort: "date" }));

// Functional update
setParams((prev) => {
  prev.set("page", "2");
  return prev;
});

useRouter

Returns the current router context.

const router = useRouter();
console.log(router.base); // "/"

Pattern Matching

Match paths directly without hooks or components.

import { matchPath, matchRoute } from "@w3cj/ruta";

const { matched, params } = matchPath("/users/:id", "/users/42");
// matched: true, params: { id: "42" }

const result = matchPath("/files/*", "/files/a/b/c");
// result.matched: true, result.params: { "*": "a/b/c" }

const match = matchRoute(/^\/post-(?<slug>\w+)$/, "/post-hello");
// match.matched: true, match.params: { slug: "hello" }

Path Utilities

import { absolutePath, relativePath, sanitizeSearch } from "@w3cj/ruta";

absolutePath("/dashboard", "/app"); // "/app/dashboard"
absolutePath("~/home", "/app"); // "/home" (~ bypasses base)

relativePath("/app", "/app/dashboard"); // "/dashboard"

sanitizeSearch("?foo=bar"); // "foo=bar"

Manual Route Registration

You can use Switch and Route to create the route tree manually if needed, but you will not get type-safety.

import { Link, Route, Switch, useParams } from "@w3cj/ruta";

const App = () => (
  <>
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
    </nav>

    <Switch>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/users/:id" component={User} />
    </Switch>
  </>
);

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const User = () => {
  const { id } = useParams<"/users/:id">();
  return (
    <h1>
      User
      {id}
    </h1>
  );
};