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

@fukict/router

v0.1.8

Published

Router for Fukict framework with nested routing and lazy loading

Readme

@fukict/router

SPA router for Fukict framework with nested routing, lazy loading, and navigation guards.

Features

  • Nested Routes: Multi-level route hierarchies with automatic depth management
  • Lazy Loading: Dynamic component loading for code splitting
  • Navigation Guards: beforeEach, afterEach, and route-level beforeEnter hooks
  • Route Parameters: Dynamic segments with :param syntax
  • Query Parameters: URL query string support
  • Hash & History Mode: Choose between hash-based or HTML5 history routing
  • Type-Safe: Full TypeScript support with route type inference
  • Link Component: Declarative navigation with automatic active states
  • Programmatic Navigation: push(), replace(), back(), forward() methods
  • Route Meta: Attach custom metadata to routes

Installation

pnpm add @fukict/router @fukict/basic

Quick Start

Basic Setup

import { Fukict, attach } from '@fukict/basic';
import { Link, RouteComponent, RouterProvider } from '@fukict/router';

// Define page components
class HomePage extends RouteComponent {
  render() {
    return (
      <div>
        <h1>Home Page</h1>
        <Link to="/about">Go to About</Link>
      </div>
    );
  }
}

class AboutPage extends RouteComponent {
  render() {
    return <h1>About Page</h1>;
  }
}

// Configure routes
const routes = [
  { path: '/', component: HomePage },
  { path: '/about', component: AboutPage },
];

// Create app with RouterProvider
class App extends Fukict {
  render() {
    return (
      <div>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
        </nav>
        <RouterProvider routes={routes} />
      </div>
    );
  }
}

attach(<App />, document.getElementById('app')!);

Core Concepts

Route Configuration

import type { RouteConfig } from '@fukict/router';

const routes: RouteConfig[] = [
  {
    path: '/',
    component: HomePage,
    meta: { title: 'Home' },
  },
  {
    path: '/users/:id',
    component: UserPage,
    meta: { title: 'User Profile', requiresAuth: true },
    beforeEnter: (to, from, next) => {
      // Route-level guard
      if (isAuthenticated()) {
        next();
      } else {
        next('/login');
      }
    },
  },
  {
    path: '/dashboard',
    component: DashboardLayout,
    children: [
      {
        path: '/dashboard/overview',
        component: OverviewPage,
      },
      {
        path: '/dashboard/settings',
        component: SettingsPage,
      },
    ],
  },
  {
    path: '*',
    component: NotFoundPage,
  },
];

RouterProvider Options

import { RouterProvider } from '@fukict/router';

<RouterProvider
  routes={routes}
  mode="hash" // or "history"
  beforeEach={(to, from, next) => {
    // Global before guard
    console.log('Navigating:', from.path, '->', to.path);
    document.title = to.meta?.title || 'App';
    next();
  }}
  afterEach={(to, from) => {
    // Global after hook
    console.log('Navigation complete');
  }}
/>;

RouteComponent Base Class

Extend RouteComponent for convenient access to routing APIs:

import { RouteComponent } from '@fukict/router';

class UserPage extends RouteComponent {
  mounted() {
    // Access route information
    console.log('Route path:', this.route.path);
    console.log('Params:', this.params);
    console.log('Query:', this.query);
  }

  // Listen to parameter changes
  routeParamsChanged(newParams: any, oldParams: any) {
    console.log('Params changed:', oldParams, '->', newParams);
    // Re-fetch data based on new params
    this.loadUser(newParams.id);
  }

  // Listen to query changes
  routeQueryChanged(newQuery: any, oldQuery: any) {
    console.log('Query changed:', oldQuery, '->', newQuery);
  }

  loadUser(id: string) {
    // Load user data
  }

  goHome = () => {
    this.push('/');
  };

  render() {
    const { id } = this.params;
    const { page = '1' } = this.query;

    return (
      <div>
        <h1>User {id}</h1>
        <p>Page: {page}</p>
        <button on:click={this.goHome}>Go Home</button>
      </div>
    );
  }
}

Link Component

import { Link } from '@fukict/router';

// Basic link
<Link to="/about">About</Link>

// With query parameters
<Link to={{ path: '/search', query: { q: 'fukict', page: '1' } }}>
  Search
</Link>

// Replace mode (doesn't add history entry)
<Link to="/login" replace>Login</Link>

// Custom active class
<Link to="/" activeClass="nav-active" exactActiveClass="nav-exact">
  Home
</Link>

// Styled link
<Link to="/profile" className="nav-link">
  <span>Profile</span>
</Link>

Programmatic Navigation

class MyComponent extends RouteComponent {
  handleSubmit = () => {
    // Push new route
    this.push('/success');

    // Push with query
    this.push({ path: '/search', query: { q: 'test' } });

    // Replace current route
    this.replace('/login');

    // Update query only
    this.updateQuery({ page: '2', sort: 'date' });

    // Go back
    this.back();

    // Go forward
    this.forward();

    // Access router directly
    this.router.push('/custom');
  };

  render() {
    return <button on:click={this.handleSubmit}>Submit</button>;
  }
}

Nested Routes

Parent-Child Structure

// Parent layout component
class DashboardLayout extends RouteComponent {
  render() {
    return (
      <div class="dashboard">
        <aside>
          <Link to="/dashboard/overview">Overview</Link>
          <Link to="/dashboard/analytics">Analytics</Link>
          <Link to="/dashboard/settings">Settings</Link>
        </aside>
        <main>
          {/* Render child routes here */}
          <RouterView router={this.router} />
        </main>
      </div>
    );
  }
}

// Child components
class OverviewPage extends RouteComponent {
  render() {
    return <h2>Dashboard Overview</h2>;
  }
}

class AnalyticsPage extends RouteComponent {
  render() {
    return <h2>Analytics</h2>;
  }
}

// Route configuration
const routes = [
  {
    path: '/dashboard',
    component: DashboardLayout,
    children: [
      { path: '/dashboard/overview', component: OverviewPage },
      { path: '/dashboard/analytics', component: AnalyticsPage },
      { path: '/dashboard/settings', component: SettingsPage },
    ],
  },
];

Navigation Guards

Global Guards

<RouterProvider
  routes={routes}
  beforeEach={(to, from, next) => {
    // Authentication check
    if (to.meta?.requiresAuth && !isLoggedIn()) {
      next('/login');
      return;
    }

    // Update page title
    document.title = to.meta?.title || 'App';

    // Proceed
    next();
  }}
  afterEach={(to, from) => {
    // Analytics tracking
    trackPageView(to.path);

    // Scroll to top
    window.scrollTo(0, 0);
  }}
/>

Route-Level Guards

const routes = [
  {
    path: '/admin',
    component: AdminPage,
    beforeEnter: (to, from, next) => {
      if (isAdmin()) {
        next();
      } else {
        next('/forbidden');
      }
    },
  },
];

Guard Execution Order

Global beforeEach
    ↓
Route beforeEnter
    ↓
Component mount/update
    ↓
Global afterEach

Route Parameters

Dynamic Segments

// Route config
{ path: '/users/:id', component: UserPage }
{ path: '/posts/:category/:id', component: PostPage }

// Access in component
class UserPage extends RouteComponent {
  render() {
    const { id } = this.params;
    return <h1>User {id}</h1>;
  }

  // React to param changes
  routeParamsChanged(newParams: any, oldParams: any) {
    console.log('User changed from', oldParams.id, 'to', newParams.id);
  }
}

Query Parameters

// Navigate with query
this.push({ path: '/search', query: { q: 'fukict', page: '1' } });

// Access in component
class SearchPage extends RouteComponent {
  render() {
    const { q, page = '1' } = this.query;
    return (
      <div>
        <h1>Search: {q}</h1>
        <p>Page: {page}</p>
      </div>
    );
  }

  // Update query
  nextPage = () => {
    const currentPage = parseInt(this.query.page || '1');
    this.updateQuery({ page: String(currentPage + 1) });
  };

  // React to query changes
  routeQueryChanged(newQuery: any, oldQuery: any) {
    this.performSearch(newQuery.q, newQuery.page);
  }
}

Lazy Loading

// Define lazy component loader
const LazyPage = () => import('./pages/LazyPage');

// Route configuration
const routes = [
  {
    path: '/lazy',
    component: LazyPage, // Will be loaded on demand
  },
];

Route Metadata

// Define custom route meta
interface RouteMeta {
  title?: string;
  requiresAuth?: boolean;
  roles?: string[];
}

// Use in routes
const routes: RouteConfig[] = [
  {
    path: '/admin',
    component: AdminPage,
    meta: {
      title: 'Admin Panel',
      requiresAuth: true,
      roles: ['admin'],
    } as RouteMeta,
  },
];

// Access in guards
beforeEach: (to, from, next) => {
  const meta = to.meta as RouteMeta;
  if (meta?.requiresAuth && !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
};

Advanced Usage

Router Instance

import { Router } from '@fukict/router';

// Create router manually
const router = new Router({
  mode: 'history',
  routes,
  beforeEach: (to, from, next) => {
    // Guard logic
    next();
  },
});

// Use in app
class App extends Fukict {
  render() {
    return <RouterView router={router} />;
  }
}

Custom Link Styling

<Link
  to="/profile"
  activeClass="text-blue-500"
  exactActiveClass="font-bold text-blue-700"
>
  <span>Profile</span>
</Link>

Redirect Routes

const routes = [
  { path: '/', component: HomePage },
  {
    path: '/old-path',
    beforeEnter: (to, from, next) => {
      next('/new-path');
    },
  },
  { path: '/new-path', component: NewPage },
];

Best Practices

1. Use RouteComponent Base Class

// ✅ Good: Extend RouteComponent
class UserPage extends RouteComponent {
  render() {
    return <div>User {this.params.id}</div>;
  }
}

// ❌ Bad: Manual router access
class UserPage extends Fukict<{ router: Router }> {
  render() {
    const params = this.props.router.currentRoute.params;
    return <div>User {params.id}</div>;
  }
}

2. Handle Parameter Changes

// ✅ Good: Implement routeParamsChanged
class ProductPage extends RouteComponent {
  routeParamsChanged(newParams: any, oldParams: any) {
    this.loadProduct(newParams.id);
  }
}

// ❌ Bad: No reaction to param changes
class ProductPage extends RouteComponent {
  mounted() {
    this.loadProduct(this.params.id);
    // Won't reload when navigating to different product
  }
}

3. Organize Routes

// ✅ Good: Modular route structure
const userRoutes = [
  { path: '/users', component: UserList },
  { path: '/users/:id', component: UserDetail },
];

const adminRoutes = [
  { path: '/admin', component: AdminDashboard },
  { path: '/admin/users', component: AdminUsers },
];

const routes = [...userRoutes, ...adminRoutes, notFoundRoute];

Examples

See the examples/infra-router for complete examples:

  • Basic routing
  • Nested routes
  • Route parameters and query strings
  • Navigation guards
  • Lazy loading
  • 404 handling

API Reference

See docs/README.md for detailed API documentation.

Related Packages

License

MIT