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

@aminnairi/react-router

v2.0.1

Published

Type-safe router for the React library

Readme

@aminnairi/react-router

Type-safe router for the React library

Requirements

Usage

Project initialization

npm create vite -- --template react-ts project
cd project

Dependencies installation

npm install

Library installation

npm install @aminnairi/react-router

Setup

mkdir src/router
mkdir src/router/pages
touch src/router/pages/home.tsx
import { createPage } from "@aminnairi/react-router";

export const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home page</h1>;
  },
});
touch src/router/fallback.tsx
import { useNavigateToPage } from "@aminnairi/react-router";
import { home } from "./pages/home";

export const Fallback = () => {
  const navigateToHomePage = useNavigateToPage(home);

  return <button onClick={() => navigateToHomePage({})}>Go back home</button>;
};
touch src/router/issue.tsx
import { Fragment } from "react";
import { useNavigateToPage } from "@aminnairi/react-router";
import { home } from "./pages/home";

export const Issue = () => {
  const navigateToHomePage = useNavigateToPage(home);

  return (
    <Fragment>
      <h1>An issue occurred</h1>
      <button onClick={() => navigateToHomePage({})}>Go back home</button>
    </Fragment>
  );
};
touch src/router/index.ts
import { createRouter } from "@aminnairi/react-router";
import { Fallback } from "./fallback";
import { Issue } from "./issue";
import { home } from "./pages/home";

export const router = createRouter({
  fallback: Fallback,
  issue: Issue,
  pages: [home],
});
touch src/App.tsx
import { router } from "./router";

export default function App() {
  return <router.View />;
}
touch src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { router } from "./router";
import App from "./App";

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element not found");
}

createRoot(rootElement).render(
  <StrictMode>
    <router.Provider>
      <App />
    </router.Provider>
  </StrictMode>,
);

Startup

npm run dev

API

createPage

Creates a new page definition that can then later be used to create a router. It takes the path of the page to create as well as the element that needs to be rendered when a client navigates to this page.

import { createPage } from "@aminnairi/react-router";

createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

You can then inject the page inside a router.

import { createPage, createRouter } from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

createRouter({
  fallback: () => <h1>Not found</h1>,
  issue: () => <h1>An error occurred</h1>,
  pages: [home],
});

You can define a page that has dynamic parameters, and get back into the element the needed parameters.

import { createPage } from "@aminnairi/react-router";

createPage({
  path: "/users/:user",
  element: function User({ parameters: { user } }) {
    return <h1>User#{user}</h1>;
  },
});

And you can have of course more than one dynamic parameter.

import { createPage } from "@aminnairi/react-router";

createPage({
  path: "/users/:user/articles/:article",
  element: function UserArticle({ parameters: { user, article } }) {
    return (
      <h1>
        Article#{article} of user#{user}
      </h1>
    );
  },
});

useNavigateToPage

You can navigate from one page from another.

import { Fragment } from "react";
import { createPage, useNavigateToPage } from "@aminnairi/react-router";

const login = createPage({
  path: "/login",
  element: function Login() {
    return <h1>Login</h1>;
  },
});

const about = createPage({
  path: "/about",
  element: function About() {
    const navigateToLoginPage = useNavigateToPage(login);

    return (
      <Fragment>
        <h1>About Us</h1>
        <button onClick={() => navigateToLoginPage({})}>Login</button>
      </Fragment>
    );
  },
});

createPage({
  path: "/",
  element: function Home() {
    const navigateToAboutPage = useNavigateToPage(about);

    return (
      <Fragment>
        <h1>Home</h1>
        <button onClick={() => navigateToAboutPage({})}>About Us</button>
      </Fragment>
    );
  },
});

And you can of course navigate to pages that have dynamic parameters as well.

import { Fragment } from "react";
import { createPage, useNavigateToPage } from "@aminnairi/react-router";

const user = createPage({
  path: "/users/:user",
  element: function User({ parameters: { user } }) {
    return <h1>User#{user}</h1>;
  },
});

createPage({
  path: "/",
  element: function Home() {
    const navigateToUserPage = useNavigateToPage(user);

    return (
      <Fragment>
        <h1>Home</h1>
        <button onClick={() => navigateToUserPage({ user: "123" })}>
          User#123
        </button>
      </Fragment>
    );
  },
});

createRouter

Creates a router that you can then use to display the view, which is the page matching the current browser's location.

import { Fragment, StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createRouter, createPage } from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

const router = createRouter({
  fallback: () => <h1>Not found</h1>,
  issue: () => <h1>An error occurred</h1>,
  pages: [home],
});

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element not found");
}

const root = createRoot(rootElement);

const App = () => {
  return (
    <Fragment>
      <header>
        <h1>App</h1>
      </header>
      <main>
        <router.View />
      </main>
      <footer>Credit © Yourself 2025</footer>
    </Fragment>
  );
};

root.render(
  <StrictMode>
    <router.Provider>
      <App />
    </router.Provider>
  </StrictMode>,
);

You can also activate the View Transition Web API if you want before each page renders. This is nice because by default, the browser already has some styling that allows for a smooth and simple transition between pages.

All you have to do is to provide a transition function in the arguments of the createRouter function. This function receives the navigation direction ("pushstate" or "popstate") and a next callback to render the next page.

This library also exports a slideFadeTransition that you can use out-of-the-box.

import { Fragment, StrictMode } from "react";
import { createRoot } from "react-dom/client";
import {
  createRouter,
  createPage,
  slideFadeTransition,
} from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Page() {
    return <h1>Home</h1>;
  },
});

const router = createRouter({
  transition: slideFadeTransition,
  fallback: () => <h1>Not found</h1>,
  issue: () => <h1>An error occurred</h1>,
  pages: [home],
});

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element not found");
}

const root = createRoot(rootElement);

const App = () => {
  return (
    <Fragment>
      <header>
        <h1>App</h1>
      </header>
      <main>
        <router.View />
      </main>
      <footer>Credit © Yourself 2025</footer>
    </Fragment>
  );
};

root.render(
  <StrictMode>
    <router.Provider>
      <App />
    </router.Provider>
  </StrictMode>,
);

The createRouter takes a functional component that allow you to react to error in case a component throws. You can use the props to get a property error containing the error that has been thrown as well as a reset function that allow you to reset the error.

import { Fragment, StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createRouter, createPage } from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

const router = createRouter({
  fallback: () => <h1>Not found</h1>,
  issue: ({ error, reset }) => (
    <Fragment>
      <h1>Error</h1>
      <p>{error.message}</p>
      <button onClick={reset}>Reset</button>
    </Fragment>
  ),
  pages: [home],
});

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element not found");
}

const root = createRoot(rootElement);

const App = () => {
  return (
    <Fragment>
      <header>
        <h1>App</h1>
      </header>
      <main>
        <router.View />
      </main>
      <footer>Credit © Yourself 2025</footer>
    </Fragment>
  );
};

root.render(
  <StrictMode>
    <router.Provider>
      <App />
    </router.Provider>
  </StrictMode>,
);

You can also define this function from the outside by using the createIssue function.

import { Fragment, StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createRouter, createPage, createIssue } from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

const Fallback = () => {
  return <h1>Not found</h1>;
};

const Issue = createIssue(({ error, reset }) => (
  <Fragment>
    <h1>Error</h1>
    <p>{error.message}</p>
    <button onClick={reset}>Reset</button>
  </Fragment>
));

const router = createRouter({
  fallback: Fallback,
  issue: Issue,
  pages: [home],
});

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element not found");
}

const root = createRoot(rootElement);

const App = () => {
  return (
    <Fragment>
      <header>
        <h1>App</h1>
      </header>
      <main>
        <router.View />
      </main>
      <footer>Credit © Yourself 2025</footer>
    </Fragment>
  );
};

root.render(
  <StrictMode>
    <router.Provider>
      <App />
    </router.Provider>
  </StrictMode>,
);

You can use a prefix for your routes, useful if you need to publish this app in a scope like GitHub Pages.

You don't have to manually append this prefix when creating pages, its automatically added for you.

import { Fragment, StrictMode } from "react";
import { createRoot } from "react-dom/client";
import {
  createRouter,
  createPage,
  createIssue,
  useNavigateToPage,
} from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

const Fallback = () => {
  const navigateToHomePage = useNavigateToPage(home);

  return (
    <Fragment>
      <h1>Not found</h1>
      <button onClick={() => navigateToHomePage({})}>Go Back Home</button>
    </Fragment>
  );
};

const Issue = createIssue(({ error, reset }) => (
  <Fragment>
    <h1>Error</h1>
    <p>{error.message}</p>
    <button onClick={reset}>Reset</button>
  </Fragment>
));

const router = createRouter({
  prefix: "/portfolio",
  fallback: Fallback,
  issue: Issue,
  pages: [home],
});

const rootElement = document.getElementById("root");

if (!rootElement) {
  throw new Error("Root element not found");
}

const root = createRoot(rootElement);

const App = () => {
  return (
    <Fragment>
      <header>
        <h1>App</h1>
      </header>
      <main>
        <router.View />
      </main>
      <footer>Credit © Yourself 2025</footer>
    </Fragment>
  );
};

root.render(
  <StrictMode>
    <router.Provider>
      <App />
    </router.Provider>
  </StrictMode>,
);

useNavigateToPage

Allow you to create a function that can then be called to navigate to another page inside a React component.

It accepts a page that has been created using createPage.

import { Fragment } from "react";
import { createPage, useNavigateToPage } from "@aminnairi/react-router";

const home = createPage({
  path: "/",
  element: function Home() {
    return <h1>Home</h1>;
  },
});

createPage({
  path: "/about",
  element: function About() {
    const navigateToHomePage = useNavigateToPage(home);

    return (
      <Fragment>
        <h1>About</h1>
        <button onClick={() => navigateToHomePage({})}>Home</button>
      </Fragment>
    );
  },
});

If this page has dynamic parameters, it forces you to provide them when called inside your component.

The parameters should always be provided as string, as they are the only data type that can be used inside a URL.

import { Fragment } from "react";
import { createPage, useNavigateToPage } from "@aminnairi/react-router";

const user = createPage({
  path: "/users/:user",
  element: function User({ parameters: { user } }) {
    return <h1>User#{user}</h1>;
  },
});

createPage({
  path: "/about",
  element: function About() {
    const navigateToUserPage = useNavigateToPage(user);

    return (
      <Fragment>
        <h1>About</h1>
        <button onClick={() => navigateToUserPage({ user: "123" })}>
          Home
        </button>
      </Fragment>
    );
  },
});

useLink

Allow you to navigate to another page using a JSX component instead of a callback as for the useNavigateToPage hook.

The created component is simply a <a href="...">{children}</a> under the hood which prevents the default behavior of the navigator which is to create a new HTTP request and to reload the page. The href attribute is computed from the page path and its parameters.

import { Fragment } from "react";
import { createPage, useLink } from "@aminnairi/react-router";

const user = createPage({
  path: "/users/:user",
  element: function User({ parameters: { user } }) {
    return <h1>User#{user}</h1>;
  },
});

createPage({
  path: "/about",
  element: function About() {
    const Link = useLink(user);

    return (
      <Fragment>
        <h1>About</h1>
        <Link parameters={{ user: "123" }}>User#123</Link>
      </Fragment>
    );
  },
});

useSearch

Allow you to get one or more search query from the URL.

This will return an instance of the URLSearchParams Web API so that you can use you existing knowledge to manipulate the search queries easily.

import { useMemo } from "react";
import { createPage, useSearch } from "@aminnairi/react-router";

createPage({
  path: "/users",
  element: function Home() {
    const search = useSearch();
    const sortedByDate = useMemo(() => search.get("sort-by") === "date", [search]);

    return (
      <h1>Users</h1>
      <p>Sorted by date: {sortedByDate ? "yes" : "no"}</p>
    );
  }
});

You cannot set the search queries for now, this will be added in future release of this library.

useHash

Allow you to get the hash, also called fragment, from the URL which is everything after the # symbol.

import { createPage, useHash } from "@aminnairi/react-router";

createPage({
  path: "/oauth/callback",
  element: function OauthCallback() {
    const token = useHash();

    return <h1>You token is {token}</h1>;
  },
});

Internal API

doesRouteMatchPath

Return a boolean in case a route matches a path. A route is a URI that looks something like /users/:user/articles and a path is the browser's location pathname that looks something like /users/123/articles.

This function is mainly used in the internals of the createRouter and in most case should not be necessary.

import { doesRouteMatchPath } from "@aminnairi/react-router";

doesRouteMatchPath("/", "/"); // true

doesRouteMatchPath("/", "/about"); // false

doesRouteMatchPath("/users/:user", "/users/123"); // true

doesRouteMatchPath("/users/:user", "/users/123/articles"); // false

You can also optionally provide a prefix.

import { doesRouteMatchPath } from "@aminnairi/react-router";

doesRouteMatchPath("/", "/github", "/github"); // true

doesRouteMatchPath("/", "/github/about", "/github"); // false

doesRouteMatchPath("/users/:user", "/github/users/123", "/github"); // true

doesRouteMatchPath("/users/:user", "/github/users/123/articles", "/github"); // false

getParameters

Return an object in case a route matches a path, with its dynamic parameters as output. It returns a generic object type in case no dynamic parameters are found in the URI. Note that the parameters are always strings, if you need to, convert them to other types explicitely.

This function is mainly used in the internals of the createRouter and in most case should not be necessary.

import { getParameters } from "@aminnairi/react-router";

getParameters("/", "/"); // {}

getParameters("/", "/about"); // {}

getParameters("/users/:user", "/users/123"); // { user: "123" }

getParameters("/users/:user", "/users/123/articles"); // {}

You can also provide an optional prefix.

import { getParameters } from "@aminnairi/react-router";

getParameters("/", "/github", "/github"); // {}

getParameters("/", "/github/about", "/github"); // {}

getParameters("/users/:user", "/github/users/123", "/github"); // { user: "123" }

getParameters("/users/:user", "/github/users/123/articles", "/github"); // {}

sanitizePath

Internal function that helps normalizing the URL by removing trailing and leading slashes as well as removing any duplicate and unecessary slashes.

import { sanitizePath } from "@aminnairi/react-router";

sanitizePath("/"); // "/"

sanitizePath("users"); // "/users"

sanitizePath("users/"); // "/users"

sanitizePath("users//123///articles"); // "/users/123/articles"

Features

TypeScript

This library has been written in TypeScript from the ground up, no manual definition types created, only pure TypeScript.

Type-safety has been the #1 goal, this means that you can fearlessly refactor your code without forgetting to update one part of your code that might break, types got you covered.

No codegen

Code generation is useful in environment where multiple languages may be used, but in the case of a Web application written in TypeScript, there is no need for any codegen at all, thus reducing the surface of errors possibly generated by such tools, and greatly reducing complexity when setting up a router.

Simplicity

This library does nothing more other than abstracting for your the complexity of using the History Web API, as well as providing you with type safety out of the box.

This means that you can use this library with other popular solutions for handling metadata for instance.

Transition

Support for the View Transition API is built-in and allows for painless and smooth view transition out-of-the-box. You can create your own transition animation, and the library also exports a slideFadeTransition ready to be used.

Error handling

Never fear having a blank page again when a component throws. This library lets you define a functional component that will answer to any error that might be raised by any pages so that you can react accordingly by providing a nice and friendly error page instead of a blank or white page.

License

See LICENSE.

Changelogs

Versions

2.0.1

Major changes

None.

Minor changes

  • Now running a linter with eslint and TypeScript and a stricter configuration to prevent type errors

Bug & security fixes

  • Fixed an error while the error boundary was not using an override when using a stricter typescript configuration

2.0.0

Major changes

  • The transition property in createRouter is now a function instead of a boolean, which allows for more control over the animation. This is a breaking change.
  • A slideFadeTransition is now exported and can be used directly.

Minor changes

None.

Bug & security fixes

None.

1.1.0

Major changes

None.

Minor changes

  • Added a new useLink hook to create components that allow for navigating to another page

Bug & security fixes

None.

1.0.1

Major changes

None.

Minor changes

None.

Bug & security fixes

  • Fixed an issue when navigating to a page that already starts with a slash

1.0.0

Major changes

  • The arguments of findPage have moved from an object to regular arguments, with the first one being the path, and the second being the current route
  • Removed the page.navigate property in favor of the new useNavigateTo hook
  • The createPage now returns the page directly instead of exposing it in an object

Minor changes

  • Added a Provider component from the created router which exposes variables for the children such as the location
  • Added a new useIsActivePage hook for the created router which helps computing if a given page is active or not
  • Added a new useSearch hook for the created router for getting search parameters from the current URL
  • Added a new useHash hook for the created router for getting the URL fragment
  • Added a new sanitizePath function for removing unecessary and extra slashes in a given string
  • Added a new useNavigateTo hook that replaces the old page.navigate function
  • Added a prefix property in order to use prefix for routes that need it like GitHub Pages

0.1.1

Major changes

None.

Minor changes

None.

Bug & security fixes

Fixed peer dependency for react.

0.1.0

Major changes

None.

Minor changes

None.

Bug & security fixes

None.