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

airx-router

v0.7.2

Published

Front-end routing for airx

Readme

router npm build status

Front-end routing for airx

Install

npm install airx-router

Requires airx as a peer dependency:

npm install airx

Use

import * as airx from 'airx'
import { RouteComponentProps, Router } from 'airx-router'

function TestRouteComponent(props: RouteComponentProps) {
  // Matching child routes will be rendered here
  return () => props.children
}

const route = {
  path: '/root',
  component: TestRouteComponent,
  children: [
    {
      path: '/',
      redirect: 'child-1'
    },
    {
      path: 'child-1',
      component: TestRouteComponent,
      children: [
        {
          path: '/',
          redirect: 'child-2'
        },
        {
          path: 'child-2',
          component: TestRouteComponent
        }
      ]
    }
  ]
}

airx
  .createApp(<Router routes={[route]} />)
  .mount(document.getElementById('app'))

API

<Router>

The main router component that manages routing state and renders matched routes.

Props

  • routes: Route[] | Route - Route configuration, can be a single route or array
  • history?: History - Optional history instance (defaults to createBrowserHistory())

Returns

A render function that returns the current matched route element.

Route Types

// Path route - renders a component
interface PathRoute {
  path: string
  name?: string
  meta?: Record<string, unknown>
  component: AirxComponent<RouteComponentProps>
  children?: Route[]
}

// Redirect route - redirects to another path
interface RedirectRoute {
  path: string
  name?: string
  meta?: Record<string, unknown>
  redirect: string
}

RouteComponentProps

interface RouteComponentProps {
  data: MatchResult      // Path matching results with params
  children: AirxElement<RouteComponentProps>[]  // Nested child route elements
}

useRouter()

Hook to access the history instance from anywhere in the component tree:

import { useRouter } from 'airx-router'

function MyComponent() {
  const history = useRouter()
  
  const handleClick = () => {
    history.push('/new-path')
  }
  
  return <button onClick={handleClick}>Navigate</button>
}

Path Matching

Static Paths

{ path: '/page', component: PageComponent }

Matches exactly /page.

Root Path

{ path: '/', component: HomeComponent }

Matches the root path /.

Dynamic Segments

{ path: '/user/:id', component: UserComponent }
// Matches: /user/1, /user/abc, etc.
// data.params.id will contain the matched value

Optional Segments

{ path: '/page/:tab?', component: PageComponent }
// Matches: /page, /page/settings, etc.

Catch-all

{ path: '/:**', component: NotFoundComponent }
// Matches any unmatched path

Nested Paths

Child paths are matched relative to their parent:

{
  path: '/parent',
  component: ParentComponent,
  children: [
    { path: 'child', component: ChildComponent }  // Matches /parent/child
  ]
}

Redirects

Absolute Redirect

{ path: '/old', redirect: '/new' }

Relative Redirect

{
  path: '/parent',
  component: ParentComponent,
  children: [
    { path: '/', redirect: 'child' }  // Redirects to /parent/child
  ]
}

Path Traversal

{ path: '/a/b', redirect: '../c' }  // Redirects to /a/c

History Modes

Browser History (default)

Uses HTML5 History API:

<Router routes={routes} />

Memory History

Useful for testing or server-side rendering:

import { createMemoryHistory } from 'history'

const history = createMemoryHistory({ initialEntries: ['/'] })
<Router routes={routes} history={history} />

Hash History

For environments without server configuration:

// Use hash-based paths
const history = createMemoryHistory({ initialEntries: ['#/page'] })
<Router routes={routes} history={history} />

Route Meta

Routes can include metadata for guards, permissions, etc:

{
  path: '/admin',
  component: AdminComponent,
  meta: {
    requiresAuth: true,
    roles: ['admin'],
    title: 'Admin Panel'
  }
}

Type Guards

import { isRedirectRoute, isPathRoute } from 'airx-router'

if (isRedirectRoute(route)) {
  // route.redirect is available
} else if (isPathRoute(route)) {
  // route.component and route.children are available
}

Limitations

Context Requirement

The Router must be used within an airx component context. It uses provide/inject for history distribution.

Single Router Instance

Currently, only one Router instance per app is supported.

No Built-in Guards

This library focuses on routing; authentication/authorization guards should be implemented at the component level or via a wrapper.

Path Format

  • Paths must start with / for root-relative matching
  • Child paths should not start with / (they're relative to parent)
  • Empty path '' is treated as equivalent to /

Signal Dependency

Requires signal-polyfill or native Signal support in the runtime environment.

Testing

Run tests with:

npm test        # Watch mode
npm run test:run # Single run
npm run test:ui  # UI mode

License

MIT