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

easy-page-router

v1.0.1

Published

easy-page-router is a lightweight and easy-to-use JavaScript routing package that simplifies navigation in vanilla JavaScript, React, and React Native applications.

Readme

easy-page-router

npm version bundle size license TypeScript

easy-page-router is an ultra-lightweight (< 1 kB min+gzip), universal routing library for React, React Native, and SSR. Designed for maximum simplicity, zero boilerplate, and effortless page transition animations.


Why easy-page-router?

  • ⚡ Ultra-lightweight (< 1 kB) — Zero bloat, zero heavy dependencies.
  • 🌐 Truly Universal — Same declarative API across React Web, React Native, and Node.js SSR.
  • 🎬 First-class Page Animations — Retains page history states (active, back, forward) so page transitions just work.
  • 🚀 Zero Boilerplate — Route matching using simple functions without verbose configuration trees.
  • 🔒 Node.js 22/24 Strict ESM Ready — Built with modern ES Modules and full TypeScript declarations.

Installation

npm install easy-page-router
pnpm add easy-page-router
# or
yarn add easy-page-router

Quickstart (React Web)

Copy and use immediately in your React app:

import React from "react";
import { RouterProvider, Router, useRouter } from "easy-page-router";
import { Link, useTitle } from "easy-page-router/react";

export function App() {
  return (
    <RouterProvider>
      <nav style={{ display: "flex", gap: "1rem" }}>
        <Link href="/" styleActive={{ fontWeight: "bold", color: "#0066cc" }}>
          Home
        </Link>
        <Link href="/songs/42" styleActive={{ fontWeight: "bold", color: "#0066cc" }}>
          Song #42
        </Link>
        <Link href="/about" styleActive={{ fontWeight: "bold", color: "#0066cc" }}>
          About
        </Link>
      </nav>

      <main>
        <Router
          renderPage={({ path, searchParams }) => {
            if (path.length === 0) {
              return <HomePage />;
            }
            if (path[0] === "songs" && path[1]) {
              return <SongPage id={path[1]} filter={searchParams.filter} />;
            }
            if (path[0] === "about") {
              return <AboutPage />;
            }
            return <NotFoundPage />;
          }}
        />
      </main>
    </RouterProvider>
  );
}

function HomePage() {
  useTitle("Home - My App");
  return <h1>Welcome Home</h1>;
}

function SongPage({ id, filter }: { id: string; filter?: string }) {
  useTitle(`Song #${id}`);
  const { push, replace, back } = useRouter();

  return (
    <div>
      <h1>Playing Song #{id}</h1>
      {filter && <p>Filter: {filter}</p>}
      <button onClick={() => back()}>Go Back</button>
      <button onClick={() => push("/songs/43")}>Next Song</button>
      <button onClick={() => replace("/songs/99")}>Switch (Replace)</button>
    </div>
  );
}

function AboutPage() {
  useTitle("About Us");
  return <h1>About Page</h1>;
}

function NotFoundPage() {
  return <h1>404 - Page Not Found</h1>;
}

Page Transition Animations

easy-page-router keeps track of the page lifecycle with state ("active" | "back" | "forward"). Pages remain mounted during transition so you can animate them effortlessly via CSS:

import React, { useEffect, useState } from "react";
import { Router, RouterProvider, RenderAnimationProps } from "easy-page-router";

function AnimatedApp() {
  return (
    <RouterProvider>
      <Router
        renderAnimation={({ page, state }: RenderAnimationProps) => (
          <div className={`page-wrapper ${state}`}>{page}</div>
        )}
        renderPage={({ path }) => {
          if (path.length === 0) return <HomePage />;
          if (path[0] === "detail") return <DetailPage />;
          return <NotFoundPage />;
        }}
      />
    </RouterProvider>
  );
}

Example CSS:

.page-wrapper {
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.page-wrapper.active {
  opacity: 1;
  transform: translateX(0);
}
.page-wrapper.back {
  opacity: 0;
  transform: translateX(-100%);
  pointer-events: none;
}
.page-wrapper.forward {
  opacity: 0;
  transform: translateX(100%);
  pointer-events: none;
}

React Native

Use easy-page-router/react-native for native mobile apps:

import React from "react";
import { View, Text } from "react-native";
import { RouterProvider, Router, useRouter } from "easy-page-router";
import { Link, useHardwareBackHandler, useDeepLinking } from "easy-page-router/react-native";

export default function NativeApp() {
  return (
    <RouterProvider>
      <MainNavigator />
    </RouterProvider>
  );
}

function MainNavigator() {
  // Automatically handle Android hardware back button & deep links
  useHardwareBackHandler();
  useDeepLinking();

  return (
    <View style={{ flex: 1 }}>
      <Router
        renderPage={({ path }) => {
          if (path.length === 0) return <HomeScreen />;
          if (path[0] === "settings") return <SettingsScreen />;
          return <NotFoundScreen />;
        }}
      />
    </View>
  );
}

function HomeScreen() {
  return (
    <View style={{ padding: 20 }}>
      <Text>Home Screen</Text>
      <Link
        href="/settings"
        style={{ padding: 12, backgroundColor: "#e0e0e0", borderRadius: 8, marginTop: 16 }}
        styleActive={{ backgroundColor: "#0066cc" }}
      >
        <Text>Go to Settings</Text>
      </Link>
    </View>
  );
}

function SettingsScreen() {
  const { back } = useRouter();
  return (
    <View style={{ padding: 20 }}>
      <Text>Settings Screen</Text>
      <Link href="/" onPress={() => back()}>
        <Text>Back to Home</Text>
      </Link>
    </View>
  );
}

function NotFoundScreen() {
  return <Text>Screen Not Found</Text>;
}

Server-Side Rendering (SSR)

1. One-liner window mock (Recommended)

Simply set window.location to new URL(req.url) in your server request handler before rendering:

import { renderToString } from "react-dom/server";
import { RouterProvider, Router } from "easy-page-router";

// 1 line in your SSR handler (e.g. Express / Fastify / Koa):
globalThis.window = { location: new URL(req.url, "http://localhost") } as any;

function renderServer() {
  return renderToString(<App />);
}

2. Explicit initialHref prop

Alternatively, pass initialHref directly to <RouterProvider>:

function renderServer(reqUrl: string) {
  return renderToString(
    <RouterProvider initialHref={reqUrl}>
      <App />
    </RouterProvider>
  );
}

API Reference

<RouterProvider>

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | initialHref | string | undefined | Initial URL (ideal for SSR and tests). | | scrollSpeed | number | 500 | Smooth scroll duration in ms on page change. | | scrollAlways | boolean | false | Scroll to top on every navigation (including back/forward). | | noScroll | boolean | false | Disable automatic scroll to top. | | onPageChange | (href: string) => void | undefined | Callback triggered whenever the current page changes. | | children | ReactNode | — | Your application tree. |

<Router>

| Prop | Type | Description | | :--- | :--- | :--- | | renderPage | (props: PageProps) => ReactNode | Renders the page component matching the current route. | | renderAnimation | (props: RenderAnimationProps) => ReactNode | (Optional) Wrapper to render all active/historical pages for transitions. |

useRouter() Hook

Returns:

type RouterHook = {
  // Page info
  path: string[];                      // URL segments: "/songs/42" -> ["songs", "42"]
  searchParams: Record<string, string>;// Query params: "?view=grid" -> { view: "grid" }
  state: "active" | "back" | "forward";// Lifecycle state in history stack
  href: string;                        // Full URL (e.g. "http://localhost/songs/42?view=grid")
  to: string;                          // Path + search (e.g. "/songs/42?view=grid")
  pageKey: string;                     // Unique key of the page instance

  // Navigation actions
  push: (to: string) => void;          // Navigate to new URL
  replace: (to: string) => void;       // Replace current URL in history
  back: () => void;                    // Navigate back in history stack
  forward: () => void;                 // Navigate forward in history stack
};

<Link> (React Web)

Import from easy-page-router/react. Extends standard <a> HTML attributes.

| Prop | Type | Description | | :--- | :--- | :--- | | href | string | Target route. | | className | string | Class name. Automatically appends "active" when route matches. | | style | CSSProperties | Regular inline style. | | styleActive | CSSProperties | Style merged when the link is active. | | onClick | (e) => void | Custom click handler. Default browser navigation prevented on primary click. |

<Link> (React Native)

Import from easy-page-router/react-native. Extends React Native PressableProps.

| Prop | Type | Description | | :--- | :--- | :--- | | href | string | Target route. | | style | StyleProp<ViewStyle> | Base view style. | | styleActive | StyleProp<ViewStyle> | Style merged when active. | | onPress | (e) => void | Custom press handler. |

React Native Helpers

  • useHardwareBackHandler(enabled = true) — Automatically captures Android hardware back press and calls back().
  • useDeepLinking() — Automatically listens for app launch URLs and incoming links via Linking.

Web Utilities

  • useTitle(title: string) — Sets document.title when the page is active.
  • scrollToTop(time?: number) — Smoothly scrolls window to top over time ms.

License

MIT © Filip Paulů