Reactive router for IO-Reactive framework – a powerful, lightweight, and fully typed router for building modern SPA applications.
- 🚀 Lightweight – Minimal bundle size
- ⚡ Fast – Optimized for maximum performance
- 🔄 Reactive – Automatic updates on state change
- 📱 SPA Ready – Full single-page application support
- 🎨 Animations – Built-in page transitions
- 🔗 Smart Links – Automatic active state management
- 📊 Nested Routes – Layout and nested routing support
- 🔍 Query Parameters – Work with URL parameters
- 🍞 Breadcrumbs – Automatic breadcrumbs generation
- 🛡️ Guards – Route protection and middleware
- 🗂️ Caching – Optimized page caching
- 🔍 SEO – Page metadata management
# First, install the core framework
npm install io-reactive
# Then install the router
npm install io-reactive-router
import { IO } from 'io-reactive';
import { IORouter, Route } from 'io-reactive-router';
// Define routes
const routes = [
{
path: '/',
template: () => new IO('div', { text: 'Home Page' }),
name: 'home',
},
{
path: '/about',
template: () => new IO('div', { text: 'About' }),
name: 'about',
},
{
path: '/users/[id]',
template: (params) => new IO('div', { text: `User ID: ${params.id}` }),
name: 'user',
},
];
// Initialize the router
const router = new IORouter({
routes: Promise.resolve(routes),
domain: 'https://myapp.com',
root: document.getElementById('app'),
});
// Start the router
router.init();
const routes = [
{
path: '/',
template: () => new IO('div', { text: 'Home' }),
name: 'home',
},
{
path: '/products',
template: () => new IO('div', { text: 'Products' }),
name: 'products',
},
];
const routes = [
{
path: '/users/[id]',
template: (params) =>
new IO('div', {
text: `User: ${params.id}`,
}),
name: 'user',
},
{
path: '/posts/[slug]/comments/[commentId]',
template: (params) =>
new IO('div', {
text: `Post: ${params.slug}, Comment: ${params.commentId}`,
}),
name: 'comment',
},
];
const routes = [
{
path: '/admin',
template: () => new IO('div', { text: 'Admin Panel' }),
name: 'admin',
children: [
{
path: '/users',
template: () => new IO('div', { text: 'User Management' }),
name: 'admin-users',
},
{
path: '/settings',
template: () => new IO('div', { text: 'Settings' }),
name: 'admin-settings',
},
],
},
];
const routes = [
{
path: '/profile',
template: () => new IO('div', { text: 'Profile' }),
name: 'profile',
params: {
isPrivate: true, // Requires authentication
redirectTo: '/login', // Redirect for unauthenticated
},
},
];
const router = new IORouter({
routes: Promise.resolve(routes),
domain: 'https://myapp.com',
root: document.getElementById('app'),
// Auth function
auth: async () => {
const token = localStorage.getItem('authToken');
return !!token;
},
// Layout for all pages
layout: (components) =>
new IO('div', {
classList: ['app-layout'],
components: [
() => new IO('header', { text: 'Header' }),
...components,
() => new IO('footer', { text: 'Footer' }),
],
}),
// Middleware for each route
middleware: ({ domain, routes, href, state }) => {
console.log('Navigating to:', href);
// Analytics, logging, etc.
},
});
// Simple navigation
router.navigate('/about');
// Navigation with query params
router.navigate('/products', {
query: { category: 'electronics', page: '1' },
});
// Navigation with state
router.navigate('/profile', {
state: { fromPage: 'dashboard' },
replace: true, // Replace current history entry
});
// Back
router.history('back');
// Forward
router.history('next');
Component for creating links with automatic active state management:
import { IOActiveLink } from 'io-reactive-router';
const navLink = new IOActiveLink('/products', {
text: 'Products',
activeClass: 'active',
exactMatch: true, // Exact path match
prefetch: true, // Prefetch page
attributes: {
'data-analytics': 'nav-products',
},
});
IOActiveLink options:
-
activeClass
– CSS class for active state -
exactMatch
– Exact path match (default: false) -
prefetch
– Prefetch page on hover -
attributes
– Additional link attributes
Component for creating layouts with nested route support:
import { IOLayout } from 'io-reactive-router';
const layout = new IOLayout({
fallback: () =>
new IO('div', {
text: 'Loading...',
classList: ['loading'],
}),
depth: 1, // Nesting depth
animations: {
enter: 'fadeIn',
exit: 'fadeOut',
},
});
Factory methods:
// Simple layout
const simpleLayout = createIOLayout({
header: () => new IO('header', { text: 'Header' }),
footer: () => new IO('footer', { text: 'Footer' }),
});
// Layout with animations
const animatedLayout = createAnimatedIOLayout({
animation: 'slide',
duration: 300,
});
Work with URL parameters:
// Get parameters
const queryParams = router.queryParams();
console.log(queryParams()); // { page: '1', filter: 'active' }
// Programmatic control
import { QueryParamsModule } from 'io-reactive-router';
const queryModule = new QueryParamsModule('https://myapp.com');
// Set parameters
queryModule.set({ page: '2', sort: 'name' });
// Get parameters
const params = queryModule.get(); // { page: '2', sort: 'name' }
// Remove parameter
queryModule.remove('sort');
Automatic breadcrumbs generation:
// Get breadcrumbs
const breadcrumbs = router.breadcrumbs();
// Breadcrumb structure
interface Breadcrumb {
path: string;
label: string;
isActive: boolean;
}
// Use in a component
const breadcrumbsComponent = new IO('nav', {
components: breadcrumbs.map(
(crumb) => () =>
new IOActiveLink(crumb.path, {
text: crumb.label,
activeClass: 'breadcrumb-active',
})
),
});
import { RouteAnimationModule, FadeAnimation } from 'io-reactive-router';
const animationModule = new RouteAnimationModule(document.getElementById('app'));
// Set animation strategy
animationModule.setAnimationStrategy(
new FadeAnimation({
duration: 300,
easing: 'ease-in-out',
})
);
import { LayoutAnimationModule } from 'io-reactive-router';
const layoutModule = new LayoutAnimationModule(document.getElementById('app'));
// Set animation for a specific level
router.setLayoutAnimationStrategy(
1,
new FadeAnimation({
duration: 500,
})
);
import { AnimationStrategy } from 'io-reactive-router';
class CustomSlideAnimation implements AnimationStrategy {
async enter(element: HTMLElement): Promise<void> {
element.style.transform = 'translateX(100%)';
element.style.transition = 'transform 0.3s ease';
await new Promise((resolve) => {
setTimeout(() => {
element.style.transform = 'translateX(0)';
setTimeout(resolve, 300);
}, 10);
});
}
async exit(element: HTMLElement): Promise<void> {
element.style.transform = 'translateX(-100%)';
await new Promise((resolve) => setTimeout(resolve, 300));
}
}
Route protection with guards:
import { RouteGuard } from 'io-reactive-router';
// Create a guard
const authGuard: RouteGuard = async (to, from, next) => {
const isAuthenticated = await checkAuth();
if (isAuthenticated) {
next(); // Allow navigation
} else {
next('/login'); // Redirect to login
}
};
// Add guard for a specific route
router.addRouteGuard('/profile', authGuard);
// Global guard for all routes
router.addGlobalGuard(authGuard);
Performance optimization via caching:
// Caching is enabled by default
// Prefetch a page
await router.prefetch('/products');
// Clear cache
router.clearCache();
Manage page metadata:
// Set metadata for a route
router.setRouteMetadata('/products', {
title: 'Our Products - Online Store',
description: 'Wide selection of quality products at affordable prices',
keywords: ['products', 'online store', 'shopping'],
openGraph: {
title: 'Our Products',
description: 'Best products for you',
image: 'https://myapp.com/og-products.jpg',
url: 'https://myapp.com/products',
},
twitter: {
card: 'summary_large_image',
title: 'Our Products',
description: 'Best products for you',
},
});
// Get current metadata
const currentMeta = router.getCurrentMetadata();
const routes = [
{
path: '/dashboard',
template: () => import('./pages/Dashboard').then((m) => m.Dashboard),
name: 'dashboard',
},
];
const adminLayout = (components) =>
new IO('div', {
classList: ['admin-layout'],
components: [() => new IO('aside', { text: 'Admin Menu' }), () => new IO('main', { components })],
});
const publicLayout = (components) =>
new IO('div', {
classList: ['public-layout'],
components: [() => new IO('header', { text: 'Public Header' }), () => new IO('main', { components })],
});
// Using different layouts
const routes = [
{
path: '/admin',
template: () => new IO('div', { text: 'Admin' }),
layout: adminLayout,
},
{
path: '/',
template: () => new IO('div', { text: 'Home' }),
layout: publicLayout,
},
];
// Get navigation state
const navState = router.getNavigationState();
// Pass state during navigation
router.navigate('/profile', {
state: {
previousPage: 'dashboard',
userAction: 'edit',
},
});
new IORouter(config: iIORouter)
-
init()
– Initialize the router -
navigate(path, options?)
– Programmatic navigation -
history(direction)
– History navigation -
breadcrumbs()
– Get breadcrumbs -
queryParams()
– Work with query parameters -
addRouteGuard(path, guard)
– Add guard for a route -
addGlobalGuard(guard)
– Add global guard -
setRouteMetadata(path, metadata)
– Set metadata -
getCurrentMetadata()
– Get current metadata -
getCurrentRoute()
– Get current route -
getNavigationState()
– Get navigation state -
prefetch(path)
– Prefetch page
interface iRoute {
path: string;
template: routeIO;
name: string;
params?: {
isPrivate?: boolean;
redirectTo?: string;
};
children?: iRoute[];
}
Full TypeScript support with strict typing:
import type {
iIORouter,
iRoute,
iRoutes,
path,
routeIO,
AnimationStrategy,
RouteGuard,
RouteMetadata,
} from 'io-reactive-router';
// Typed routes
const routes: iRoute[] = [
{
path: '/',
template: () => new IO('div', { text: 'Home' }),
name: 'home',
},
];
// Typed config
const config: iIORouter = {
routes: Promise.resolve(routes),
domain: 'https://example.com',
};
/* Fade animations */
.route-enter {
opacity: 0;
}
.route-enter-active {
opacity: 1;
transition: opacity 0.3s ease;
}
.route-exit {
opacity: 1;
}
.route-exit-active {
opacity: 0;
transition: opacity 0.3s ease;
}
/* Slide animations */
.route-slide-enter {
transform: translateX(100%);
}
.route-slide-enter-active {
transform: translateX(0);
transition: transform 0.3s ease;
}
.route-slide-exit-active {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.nav-link {
color: #666;
text-decoration: none;
transition: color 0.2s ease;
}
.nav-link.active {
color: #007bff;
font-weight: bold;
}
.nav-link:hover {
color: #0056b3;
}
- Lazy Loading – Load components on demand
- Prefetching – Prefetch critical pages
- Caching – Cache rendered components
- Tree Shaking – Remove unused code
- Use
prefetch
for critical pages - Apply lazy loading for rarely used routes
- Optimize animations for better performance
- Use memoization in heavy components
// In development mode
if (process.env.NODE_ENV === 'development') {
// Log navigation
window.addEventListener('io:navigate', (event) => {
console.log('Navigation:', event.detail);
});
}
// Access router from browser console
declare global {
interface Window {
ioRouter: IORouter;
}
}
// In browser console
window.ioRouter.navigate('/debug');
console.log(window.ioRouter.getCurrentRoute());
// React Router
<Route path="/users/:id" component={UserPage} />
// IO-Router
{
path: '/users/[id]',
template: (params) => new UserPage(params.id),
name: 'user'
}
// Vue Router
{ path: '/user/:id', component: User }
// IO-Router
{
path: '/users/[id]',
template: (params) => new User(params.id),
name: 'user'
}
We welcome contributions! Please see the contributing guide.
MIT © Alexey Koh
Build modern web apps with io-reactive-router! 🚀