PraxisJS

Router

@praxisjs/router — signal-based client-side router. Configure routes with @RouterConfig, define lazy routes with @Lazy, annotate pages with @Route, and inject reactive router state with @InjectRouter, @Params, @Query, @Location.

Router

Signal-based client-side routing. Configure routes on your root component with @RouterConfig, annotate page components with @Route, and access reactive router state with injection decorators.

npm install @praxisjs/router
pnpm add @praxisjs/router
yarn add @praxisjs/router
bun add @praxisjs/router

Setup with @RouterConfig

Apply @RouterConfig to the root component to configure the router. Pass an array of page classes (decorated with @Route) or explicit route definition objects:

import { RouterConfig, Route, Lazy } from '@praxisjs/router'
import { RouterView } from '@praxisjs/router'
import { Home } from './pages/Home'   // has @Route('/')
import { About } from './pages/About' // has @Route('/about')

@RouterConfig([
  Home,                               // path read from @Route
  About,                              // path read from @Route
  { path: '/users/:id',  component: Lazy(() => import('./pages/UserDetail')) },
  {
    path: '/admin',
    component: Lazy(() => import('./pages/AdminLayout')),
    children: [
      { path: 'users',    component: Lazy(() => import('./pages/AdminUsers')) },
      { path: 'settings', component: Lazy(() => import('./pages/AdminSettings')) },
    ],
  },
  { path: '**', component: Lazy(() => import('./pages/NotFound')) },
])
@Component()
class App extends StatefulComponent {
  render() {
    return (
      <div>
        <NavBar />
        <RouterView />  {/* renders the matched route component */}
      </div>
    )
  }
}
Storybook
Live demo — Router

@Route(path)

Co-locates the route path with the page component. Pass the decorated class directly to @RouterConfig — the path is read automatically.

// pages/About.tsx
@Route('/about')
@Component()
export default class About extends StatefulComponent {
  render() { return <main>About</main> }
}
// app.tsx
@RouterConfig([About])  // path from @Route('/about')
@Component()
class App extends StatefulComponent { /* ... */ }

You can still use the object form when you need children, beforeEnter, or other options:

@RouterConfig([
  { path: About.__routePath, component: About, beforeEnter: authGuard },
])

@Lazy(loader)

Marks a class as a lazy-loaded route component. The bundle is loaded on first navigation and cached for subsequent visits.

{ path: '/users/:id', component: Lazy(() => import('./pages/UserDetail')) }

Default export required

Pages loaded via Lazy must use export default. The loader resolves module.default at runtime.


Injecting router state

Use these field decorators to access reactive router state from any component.

@InjectRouter() — navigation and loading state

import { InjectRouter } from '@praxisjs/router'

@Component()
class NavBar extends StatefulComponent {
  @InjectRouter() router!: Router

  render() {
    return (
      <nav>
        {() => this.router.loading() && <Spinner />}
        <button onClick={() => this.router.push('/home')}>Home</button>
        <button onClick={() => this.router.push('/about')}>About</button>
      </nav>
    )
  }
}

@Params() — route parameters

@Params() params!: Computed<RouteParams>

render() {
  return <p>User ID: {() => this.params().id}</p>
}

@Query() — URL query string

@Query() query!: Computed<RouteQuery>

render() {
  return <p>Page: {() => this.query().page ?? '1'}</p>
}

@Location() — full route location

@Location() location!: Signal<RouteLocation>

render() {
  return <p>Path: {() => this.location().path}</p>
}

RouteLocation has path, params, query, and hash.


await this.router.push('/users/42')              // navigate, push to history
await this.router.push('/search', { q: 'foo' }) // with query params
await this.router.replace('/login')              // replace current history entry
this.router.back()
this.router.forward()
this.router.go(-2)

import { Link } from '@praxisjs/router'

<Link to="/home">Home</Link>
<Link to="/users" activeClass="nav-active">Users</Link>
<Link to="/settings" replace>Settings</Link>
PropTypeDescription
tostringTarget path
replacebooleanReplace current entry instead of pushing
activeClassstringCSS class added when the route is active

Add beforeEnter to any route definition to run logic before navigation completes. Return true to allow, false to cancel, or a path string to redirect:

{
  path: '/admin',
  component: AdminPage,
  beforeEnter: async (to, from) => {
    const ok = await auth.checkPermission(to.path)
    return ok ? true : '/login'
  },
}

On this page