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

@natemac/app-router

v1.0.2

Published

An **abstract app-router** that works seamlessly across **Next.js 15 App Router** and **React Router v7**. This library exposes a **unified navigation interface**, **router events**, and **standardized access** to path/params/query so your app code does

Readme

🧭 App Router Abstraction

An abstract app-router that works seamlessly across Next.js 15 App Router and React Router v7.
This library exposes a unified navigation interface, router events, and standardized access to path/params/query so your app code doesn’t care which router it’s running on.

Why this exists: shared UI across web (Next.js) and SPA (React Router) often ends up with if/else routing code. This package removes those branches by providing a stable interface.


✨ Features

  • 🚦 Unified API for both Next.js and React Router
  • 🎯 Standardized router events: onNavigateStart, onNavigateEnd
  • 🔑 History key tracking per location
  • 🔄 App restart support (useful for kiosk/TV/embedded contexts)
  • 🔍 Params & search params exposed in a consistent shape
  • Route prefetch passthrough (no-op when unsupported)
  • 🧱 Small, framework-agnostic provider that you adapt with tiny wrappers

📦 Installation

npm install @natemac/app-router
# or
yarn add @natemac/app-router
pnpm add @natemac/app-router

🧰 Core Concepts

This package centers around a single provider and a context:

  • RouterProvider – wraps your app and wires your platform/router specifics.
  • RouterProviderContext – consume to use navigate, back, prefetch, etc.
  • Events – get callbacks when navigations start/end along with historyKeys.

The exposed interface (context value) is:

type RouterContextType = {
  back: () => void;
  navigate: (path: string) => void;
  params: object;
  prefetch: (route: string) => void;
  restartApp: () => void;
  searchParams: URLSearchParams;
};

Provider props:

type RouterProviderProps = {
  back: () => void;
  children: React.ReactNode;
  navigate: (path: string) => void;
  onNavigateEnd?: (e: { historyKey: string; pathname: string }) => void;
  onNavigateStart?: (e: {
    historyKey: string; // current page key
    nextHistoryKey: string; // about to navigate to this key
    pathname: string; // current pathname
  }) => void;
  params: object;
  pathname: string;
  prefetch: (route: string) => void;
  restartApp: () => void;
  searchParams: URLSearchParams;
};

Internals such as useCombinedEffect, useHistoryKey, useLocationStore are implementation details of this package and do not need to be imported in your app.


🚀 Quick Start

Wrap your app with RouterProvider and pass the functions/values from your router.

Next.js 15 (App Router)

Place this in a Client Component (add 'use client' at the top).

"use client";

import {
  useRouter,
  usePathname,
  useSearchParams,
  useParams,
} from "next/navigation";
import { RouterProvider } from "@natemac/app-router";

export default function NextRouterAdapter({
  children,
}: {
  children: React.ReactNode;
}) {
  const router = useRouter();
  const pathname = usePathname() || "/";
  const searchParams = useSearchParams();
  const params = useParams();

  return (
    <RouterProvider
      back={() => router.back()}
      navigate={(path) => router.push(path)}
      prefetch={(path) => router.prefetch?.(path)}
      restartApp={() => window.location.reload()}
      params={params}
      pathname={pathname}
      searchParams={searchParams}
      onNavigateStart={({ pathname, nextHistoryKey }) => {
        console.log("[start]", pathname, "→", nextHistoryKey);
      }}
      onNavigateEnd={({ pathname, historyKey }) => {
        console.log("[end]", pathname, "→", , historyKey);
      }}
    >
      {children}
    </RouterProvider>
  );
}

React Router v7

import {
  useLocation,
  useNavigate,
  useParams,
  useSearchParams,
} from "react-router-dom";
import { RouterProvider } from "@natemac/app-router";

export function RR7RouterAdapter({ children }: { children: React.ReactNode }) {
  const navigateRR = useNavigate();
  const location = useLocation();
  const params = useParams();
  const [search] = useSearchParams();

  return (
    <RouterProvider
      back={() => navigateRR(-1)}
      navigate={(path) => navigateRR(path)}
      prefetch={() => {
        /* no-op in RR7 */
      }}
      restartApp={() => window.location.reload()}
      params={params as object}
      pathname={location.pathname}
      searchParams={new URLSearchParams(search.toString())}
      onNavigateStart={({ pathname, nextHistoryKey }) => {
        console.log("[start]", pathname, "→", nextHistoryKey);
      }}
      onNavigateEnd={({ pathname, historyKey }) => {
        console.log("[end]", pathname, "→", historyKey);
      }}
    >
      {children}
    </RouterProvider>
  );
}

🛠 Using the Context

Consume the context anywhere in your tree:

import * as React from "react";
import { useAppRouter, useSearchParams } from "@natemac/app-router";

export function NavButtons() {
  const { navigate, back } = useAppRouter();
  const searchParams = useSearchParams();

  const id = searchParams.get("id") ?? "42";

  return (
    <div>
      <button onClick={() => back()}>Back</button>
      <button onClick={() => navigate(`/profile?id=${id}`)}>Profile</button>
    </div>
  );
}

📡 Navigation Events

Two optional callbacks let you observe navigation:

  • onNavigateStart: fires before navigation occurs.
  • onNavigateEnd: fires once the navigation is observed (history key/path updated).

Event payloads:

type NavigationEventProps = {
  historyKey: string;
  pathname: string;
};
// onNavigateStart gets { historyKey, nextHistoryKey, pathname }

Note: historyKey is a best-effort unique identifier for the current entry. Different routers expose different primitives; this package normalizes them to a string key.


🧩 Patterns & Tips

  • Prefetching: In Next.js, router.prefetch is supported; in RR7 it’s a no-op. Your app code can still call prefetch safely.
  • URLSearchParams: Standardized as URLSearchParams across both routers. Next’s ReadonlyURLSearchParams is converted in the adapter.
  • SSR: Place adapters inside client boundaries when using Next.js hooks.

🧱 Minimal Interface Surface (what you implement)

If you create your own adapter, you only need to provide:

  • back(): void
  • navigate(path: string): void
  • prefetch(route: string): void (can be a no-op)
  • restartApp(): void
  • params: object
  • pathname: string
  • searchParams: URLSearchParams

The provider takes care of emitting events and tracking history keys.


📝 License

MIT © Nathan Macfarlane