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

@koppajs/koppajs-router

v0.1.2

Published

Reusable History API router primitives for KoppaJS applications.

Readme


Purpose

The package exists to provide a deterministic, route-config-driven router runtime for KoppaJS-style applications.

It is intentionally narrow:

  • the package owns path normalization, route matching, redirect following, browser history updates, outlet rendering, metadata updates, active-link state, and scroll restoration
  • the consuming application owns route content, custom element registration, page copy, analytics, and business-specific navigation rules

Repository Classification

  • repo_type: reusable package
  • runtime_responsibility: browser router runtime
  • build_time_responsibility: TypeScript build, linting, unit tests, tarball smoke testing, and release validation
  • ui_surface: none inside the package
  • maturity_level: pre-1.0, contract-stabilizing

Installation

pnpm add @koppajs/koppajs-router
npm install @koppajs/koppajs-router

Consumer requirements:

  • a browser environment with the DOM and History API
  • an ESM-capable runtime or bundler
  • consumer-owned registration of every referenced componentTag
  • title, description, and componentTag on every final renderable route

Local repository requirements:

  • Node.js >= 22
  • pnpm >= 10.17.1

Public Contract

The public surface is deliberately small.

Main runtime surface:

  • KoppajsRouter
  • RouteDefinition, ResolvedRoute, RedirectedRoute, and related route types
  • normalizePath(), normalizeHash(), normalizeBasePath()
  • toHref() and fromLocationPathname()
  • resolveRoute() and resolveRouteByName()
  • setActiveRouteLinks() and setDocumentDescription()
  • DEFAULT_ROUTE_LINK_SELECTOR
  • DEFAULT_ROUTE_CHANGE_EVENT_NAME
  • KOPPAJS_ROUTE_CHANGE_EVENT

Runtime instance helpers:

  • router.resolve() / router.resolveByName()
  • router.navigate() / router.navigateByName()
  • router.hrefFor()
  • router.getCurrentPath() / router.getCurrentRoute()

Contract constraints:

  • flat and nested route definitions are both supported
  • route names must be unique
  • unmatched paths throw unless the consumer declares an explicit path: "*" route
  • static segments outrank dynamic segments, and dynamic segments outrank *
  • active-link matching is path-based and ignores query and hash state
  • route records are treated as immutable input data

Ownership Boundaries

Package-owned concerns:

  • route registry compilation and matching
  • named-route resolution and param interpolation
  • base-path translation between app paths and browser hrefs
  • redirect following with bounded depth
  • outlet rendering, title updates, description updates, and route-change events
  • active-link synchronization and scroll handling

Consumer-owned concerns:

  • route definitions and route metadata
  • custom element implementations referenced by componentTag
  • application copy and page composition
  • analytics, business rules, and link exclusion policy
  • browser-level end-to-end coverage in real application shells

Usage

import { KoppajsRouter, type RouteDefinition } from "@koppajs/koppajs-router";

const routes = [
  {
    path: "/",
    name: "home",
    title: "Home",
    description: "Landing page",
    componentTag: "home-page",
  },
  {
    path: "/services",
    name: "services",
    title: "Services",
    description: "Services overview",
    componentTag: "services-page",
    children: [
      {
        path: ":slug",
        name: "service-detail",
        title: "Service detail",
        description: "Service detail page",
        componentTag: "service-detail-page",
      },
    ],
  },
  {
    path: "/guides",
    name: "guides",
    redirectTo: {
      name: "guides-introduction",
    },
    children: [
      {
        path: "introduction",
        name: "guides-introduction",
        title: "Introduction",
        description: "Guide introduction",
        componentTag: "guides-introduction-page",
      },
    ],
  },
  {
    path: "*",
    name: "not-found",
    title: "Not found",
    description: "Missing page",
    componentTag: "not-found-page",
  },
] satisfies readonly RouteDefinition[];

const outlet = document.querySelector<HTMLElement>("#app-outlet");

if (!outlet) {
  throw new Error("App outlet not found.");
}

const router = new KoppajsRouter({
  routes,
  outlet,
  root: document,
  basePath: import.meta.env.BASE_URL,
  shouldSetActiveState: (link) => !link.classList.contains("nav-link--cta"),
});

router.init();

router.navigate({
  name: "service-detail",
  params: { slug: "accessibility-audit" },
  query: { ref: "nav" },
  hash: "contact-entry",
});

Runtime Behavior

Startup flow:

  • the router builds a route registry from the supplied route definitions
  • init() seeds a router-specific history-state key, attaches delegated click and popstate listeners, and starts route-link observation
  • the current browser location is resolved and rendered into the outlet

Navigation flow:

  • a direct path or named target is resolved into a normalized ResolvedRoute
  • the browser URL is translated through the configured basePath
  • history state, outlet content, metadata, active links, and the route-change event are updated together
  • scroll behavior is applied after render, including anchor navigation and saved-history restoration

Runtime Options

Browser seam options:

  • root, document, window

Routing and URL options:

  • basePath
  • routeChangeEventName
  • scrollBehavior

Active-link options:

  • linkSelector
  • activeClassName
  • activeAttributeName
  • shouldSetActiveState

Important nuance:

  • the default delegated-link selector is a[data-route]
  • active-link and click handling derive the route target from data-route when present and otherwise fall back to href
  • if you want href-only links to participate in delegated handling, provide a selector such as a[data-nav] or a[href^="/"] explicitly

Route Matching Rules

  • static path segments win over dynamic :param segments, even if declared later
  • dynamic :param segments win over * catch-all routes
  • catch-all routes preserve the unmatched browser path in path and fullPath
  • unmatched paths never fall back to the first route silently
  • redirects inherit params, query, and hash unless the redirect target replaces them

Build And Distribution

  • source lives in src/
  • pnpm run build emits the publishable package to dist/
  • the package manifest exports dist/index.js and dist/index.d.ts
  • pnpm run check:package verifies that manifest entrypoints and build output stay aligned
  • pnpm run test:package packs the tarball, installs it into a clean temporary consumer, and imports the published entrypoint
  • pnpm run check is the main local quality gate
  • pnpm run validate is the CI and release validation contract; it runs check plus the tarball-consumer smoke test

Local verification commands:

pnpm install
pnpm run check
pnpm run validate

Ecosystem Fit

This package is the routing runtime layer in the KoppaJS ecosystem.

  • koppajs-core and application shells can depend on it without inheriting website-specific route content
  • consumer applications keep ownership of route tables, component registration, and integration-level browser behavior
  • this repository intentionally does not include a demo app, Playwright suite, or UI surface of its own

Architecture & Governance

Project intent, contributor rules, and documentation contracts live in the local repo meta layer:

The file-shape contract for README.md, CHANGELOG.md, CODE_OF_CONDUCT.md, and CONTRIBUTING.md is defined in docs/specs/repository-documentation-contract.md.

Run the local document guard before committing:

pnpm run check:docs

Community & Contribution

Issues and pull requests are welcome:

https://github.com/koppajs/koppajs-router/issues

Contributor workflow details live in CONTRIBUTING.md.

Community expectations live in CODE_OF_CONDUCT.md.


License

Apache License 2.0 — © 2026 KoppaJS, Bastian Bensch