Client-Side Router
Package: @coherent.js/client
Module: /router
Since: v1.0.0-beta.2
Overview
The @coherent.js/client package includes a powerful client-side router with advanced features like route prefetching, page transitions, code splitting, and customizable scroll behavior. Perfect for building single-page applications (SPAs) with Coherent.js.
Installation
The router is included in the @coherent.js/client package:
npm install @coherent.js/client@beta
# or
pnpm add @coherent.js/client@beta
# or
yarn add @coherent.js/client@beta
Basic Usage
import { createRouter } from '@coherent.js/client/router';
// Create router
const router = createRouter({
mode: 'history', // or 'hash'
base: '/',
routes: {
'/': HomeView,
'/about': AboutView,
'/users/:id': UserView
}
});
// Navigate
router.push('/about');
router.push('/users/123');
// Go back/forward
router.back();
router.forward();
// Listen to route changes
router.on('navigate', (to, from) => {
console.log(`Navigated from ${from.path} to ${to.path}`);
});
Route Definitions
Simple Routes
const router = createRouter({
routes: {
'/': HomePage,
'/about': AboutPage,
'/contact': ContactPage
}
});
Dynamic Routes
const router = createRouter({
routes: {
'/users/:id': UserProfile,
'/posts/:slug': BlogPost,
'/category/:category/item/:id': CategoryItem
}
});
// Access params in component
function UserProfile({ params }) {
const userId = params.id;
return { div: { text: `User: ${userId}` } };
}
Nested Routes
const router = createRouter({
routes: {
'/dashboard': {
component: DashboardLayout,
children: {
'/': DashboardHome,
'/stats': DashboardStats,
'/settings': DashboardSettings
}
}
}
});
Route Prefetching
Improve performance by prefetching routes before navigation:
const router = createRouter({
prefetch: {
enabled: true,
strategy: 'hover', // 'hover', 'visible', 'eager', or 'manual'
delay: 100, // ms to wait before prefetching
maxConcurrent: 3, // max simultaneous prefetches
priority: {
critical: 100,
high: 50,
normal: 0,
low: -50
}
},
routes: {
'/': HomePage,
'/products': {
component: ProductsPage,
prefetch: 'high' // Set priority
}
}
});
Prefetch Strategies
hover: Prefetch when user hovers over a linkvisible: Prefetch when link enters viewporteager: Prefetch immediately on page loadmanual: Require manual prefetch calls
// Manual prefetching
router.prefetch('/products');
router.prefetch(['/about', '/contact']);
Page Transitions
Add smooth transitions between pages:
const router = createRouter({
transitions: {
enabled: true,
default: {
enter: 'fade-in',
leave: 'fade-out',
duration: 300
},
routes: {
'/products': {
enter: 'slide-left',
leave: 'slide-right',
duration: 400
}
}
}
});
Built-in Transitions
fade-in/fade-outslide-left/slide-rightslide-up/slide-downzoom-in/zoom-out
Custom Transitions
const router = createRouter({
transitions: {
enabled: true,
custom: {
'my-transition': {
enter: (element) => {
element.style.opacity = '0';
element.style.transform = 'translateY(20px)';
requestAnimationFrame(() => {
element.style.transition = 'all 300ms ease';
element.style.opacity = '1';
element.style.transform = 'translateY(0)';
});
},
leave: (element) => {
element.style.transition = 'all 300ms ease';
element.style.opacity = '0';
element.style.transform = 'translateY(-20px)';
}
}
}
}
});
Code Splitting
Lazy-load routes for better initial load performance:
const router = createRouter({
codeSplitting: {
enabled: true,
chunkStrategy: 'route', // or 'component'
preload: ['/', '/about'], // Routes to load immediately
maxChunkSize: 200 * 1024 // 200KB
},
routes: {
'/': HomePage,
'/products': () => import('./pages/Products.js'),
'/admin': () => import('./pages/Admin.js')
}
});
Loading States
const router = createRouter({
codeSplitting: {
enabled: true,
loadingComponent: LoadingSpinner,
errorComponent: ErrorPage
}
});
function LoadingSpinner() {
return {
div: {
className: 'loading',
text: 'Loading...'
}
};
}
Scroll Behavior
Control scroll position when navigating:
const router = createRouter({
scrollBehavior: {
behavior: 'smooth',
top: 0,
preserveScroll: false,
scrollToHash: true
}
});
Custom Scroll Behavior
const router = createRouter({
scrollBehavior: (to, from, savedPosition) => {
// Return to saved position (browser back/forward)
if (savedPosition) {
return savedPosition;
}
// Scroll to hash if present
if (to.hash) {
return {
selector: to.hash,
behavior: 'smooth'
};
}
// Scroll to top for new pages
return { x: 0, y: 0 };
}
});
Navigation Guards
Add authentication, logging, or other checks:
const router = createRouter({
routes: {
'/': HomePage,
'/dashboard': DashboardPage,
'/admin': AdminPage
}
});
// Global before guard
router.beforeEach((to, from, next) => {
if (to.path.startsWith('/admin') && !isAdmin()) {
next('/'); // Redirect to home
} else {
next(); // Continue navigation
}
});
// Global after guard
router.afterEach((to, from) => {
// Analytics
trackPageView(to.path);
});
// Per-route guard
const routes = {
'/dashboard': {
component: DashboardPage,
beforeEnter: (to, from, next) => {
if (!isAuthenticated()) {
next('/login');
} else {
next();
}
}
}
};
Router API
Navigation
// Push new route
router.push('/about');
router.push({ path: '/users/123' });
router.push({ path: '/search', query: { q: 'test' } });
// Replace current route
router.replace('/about');
// Go back/forward
router.back();
router.forward();
router.go(-2); // Go back 2 pages
router.go(1); // Go forward 1 page
Router State
// Current route
const current = router.currentRoute;
console.log(current.path); // '/users/123'
console.log(current.params); // { id: '123' }
console.log(current.query); // { tab: 'profile' }
console.log(current.hash); // '#section'
// Check if route matches
router.isActive('/about'); // true/false
Events
// Navigate event
router.on('navigate', (to, from) => {
console.log(`Navigated to ${to.path}`);
});
// Error event
router.on('error', (error) => {
console.error('Router error:', error);
});
// Prefetch events
router.on('prefetch:start', (path) => {
console.log(`Prefetching ${path}`);
});
router.on('prefetch:complete', (path) => {
console.log(`Prefetched ${path}`);
});
Complete Example
import { createRouter } from '@coherent.js/client/router';
import { render } from '@coherent.js/core';
// Create router with all features
const router = createRouter({
mode: 'history',
base: '/app',
// Route prefetching
prefetch: {
enabled: true,
strategy: 'hover',
delay: 100
},
// Page transitions
transitions: {
enabled: true,
default: {
enter: 'fade-in',
leave: 'fade-out',
duration: 300
}
},
// Code splitting
codeSplitting: {
enabled: true,
loadingComponent: LoadingSpinner
},
// Scroll behavior
scrollBehavior: {
behavior: 'smooth',
top: 0
},
// Routes
routes: {
'/': HomePage,
'/about': AboutPage,
'/products': () => import('./pages/Products.js'),
'/products/:id': ProductDetail,
'/dashboard': {
component: DashboardLayout,
beforeEnter: requireAuth,
children: {
'/': DashboardHome,
'/stats': DashboardStats
}
}
}
});
// Global guards
router.beforeEach((to, from, next) => {
// Analytics
trackPageView(to.path);
next();
});
// Start router
router.start('#app');
// Components
function HomePage() {
return {
div: {
children: [
{ h1: { text: 'Welcome' } },
{ a: { href: '/about', text: 'About', onclick: (e) => {
e.preventDefault();
router.push('/about');
}}}
]
}
};
}
function requireAuth(to, from, next) {
if (isAuthenticated()) {
next();
} else {
next('/login');
}
}
TypeScript Support
import { createRouter, Router, Route, RouteConfig } from '@coherent.js/client/router';
interface RouteParams {
id: string;
}
const router: Router = createRouter({
routes: {
'/users/:id': (({ params }: { params: RouteParams }) => {
return {
div: { text: `User ${params.id}` }
};
})
}
});
Integration with Hydration
Use with client-side hydration for optimal performance:
import { hydrateBySelector } from '@coherent.js/client';
import { createRouter } from '@coherent.js/client/router';
import { App } from './App.js';
// Hydrate server-rendered content
hydrateBySelector('#app', App);
// Start router after hydration
const router = createRouter({ /* ... */ });
router.start('#app');