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.
Maintainers
Readme
easy-page-router
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-routerpnpm add easy-page-router
# or
yarn add easy-page-routerQuickstart (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 callsback().useDeepLinking()— Automatically listens for app launch URLs and incoming links viaLinking.
Web Utilities
useTitle(title: string)— Setsdocument.titlewhen the page is active.scrollToTop(time?: number)— Smoothly scrolls window to top overtimems.
