Advanced Features
Build single-page applications with YakaJS's powerful SPA router - parameters, guards, and more
Complete guide to building SPAs with YakaJS's built-in router. Simple, powerful, and feature-rich!
const router = _.createRouter({ mode: 'history', // 'history' or 'hash' base: '/', routes: []});router.addRoute('/', { component: function() { return '<h1>Home Page</h1>'; }});router.addRoute('/about', { component: function() { return '<h1>About Page</h1>'; }});// Initialize routerrouter.init();router.addRoute('/user/:id', { component: function(params) { return `<h1>User ${params.id}</h1>`; }});// Navigate to /user/123router.navigateTo('/user/123');router.addRoute('/post/:category/:id', { component: function(params) { return ` <h1>Post in ${params.category}</h1> <p>ID: ${params.id}</p> `; }});// Navigate to /post/technology/42router.navigateTo('/post/technology/42');router.addRoute('/product/:id/:variant?', { component: function(params) { const variant = params.variant || 'default'; return `<h1>Product ${params.id} - ${variant}</h1>`; }});router.addRoute('/search', { component: function(params) { // Access query parameters const query = router.getQueryParams(); // { q: 'search term', page: '1', filter: 'active' } return ` <h1>Search Results</h1> <p>Query: ${query.q}</p> <p>Page: ${query.page || 1}</p> `; }});// Navigate with query stringrouter.navigateTo('/search?q=javascript&page=2');// Add query parametersrouter.setQueryParam('filter', 'active');router.setQueryParam('sort', 'desc');// Set multiple at oncerouter.setQueryParams({ filter: 'active', sort: 'desc', page: 2});// Remove query parameterrouter.removeQueryParam('filter');// Navigate to routerouter.navigateTo('/about');// Navigate with parametersrouter.navigateTo('/user/123');// Navigate with query stringrouter.navigateTo('/search?q=test');// Navigate with replace (no history entry)router.navigateTo('/about', { replace: true });// Go backrouter.back();// Go forwardrouter.forward();// Go to specific history entryrouter.go(-2); // Go back 2 pagesrouter.go(1); // Go forward 1 page// Define named routerouter.addRoute('/user/:id', { name: 'user-profile', component: function(params) { return `<h1>User ${params.id}</h1>`; }});// Navigate by namerouter.navigateTo({ name: 'user-profile', params: { id: 123 } });router.beforeEach(async function(to, from, next) { console.log('Navigating from', from, 'to', to); // Check authentication const isAuthenticated = await checkAuth(); if (to.path !== '/login' && !isAuthenticated) { // Redirect to login next('/login'); } else { // Allow navigation next(); }});router.afterEach(function(to, from) { console.log('Navigation complete'); // Update page title document.title = to.meta.title || 'My App'; // Track page view analytics.trackPageView(to.path); // Scroll to top window.scrollTo(0, 0);});router.addRoute('/admin', { component: function() { return '<h1>Admin Panel</h1>'; }, beforeEnter: async function(to, from, next) { const isAdmin = await checkAdminRole(); if (isAdmin) { next(); } else { next('/unauthorized'); } }});router.addRoute('/edit', { component: function() { return '<h1>Edit Form</h1>'; }, beforeLeave: async function(to, from, next) { if (hasUnsavedChanges()) { const confirmed = confirm('You have unsaved changes. Leave anyway?'); if (confirmed) { next(); } else { next(false); // Cancel navigation } } else { next(); } }});router.addRoute('/admin', { component: function() { return '<h1>Admin</h1>'; }, meta: { requiresAuth: true, requiresAdmin: true, title: 'Admin Panel', layout: 'admin' }});router.beforeEach(async function(to, from, next) { if (to.meta.requiresAuth) { const isAuth = await checkAuth(); if (!isAuth) { next('/login'); return; } } if (to.meta.requiresAdmin) { const isAdmin = await checkAdminRole(); if (!isAdmin) { next('/forbidden'); return; } } next();});router.addRoute('/users', { component: function() { return ` <h1>Users</h1> <div id="user-content"></div> `; }, children: [ { path: '', // /users component: function() { return '<p>Select a user</p>'; } }, { path: ':id', // /users/:id component: function(params) { return `<p>User ${params.id}</p>`; } }, { path: ':id/edit', // /users/:id/edit component: function(params) { return `<p>Edit user ${params.id}</p>`; } } ]});router.notFound(function() { return ` <h1>404 - Page Not Found</h1> <p>The page you're looking for doesn't exist.</p> <a href="/" onclick="router.navigateTo('/'); return false;">Go Home</a> `;});router.addRoute('/404', { component: function() { return '<h1>Page Not Found</h1>'; }});// Redirect unknown routes to 404router.beforeEach(function(to, from, next) { const routeExists = router.hasRoute(to.path); if (!routeExists) { next('/404'); } else { next(); }});<!-- Use data-route attribute for SPA navigation --><a href="/about" data-route>About</a><a href="/user/123" data-route>User Profile</a><script>// Intercept clicks on route links_(document).on('click', 'a[data-route]', function(e) { e.preventDefault(); const path = _(this).attr('href'); router.navigateTo(path);});</script>// Update active link classesrouter.afterEach(function(to) { _('a[data-route]').removeClass('active'); _(`a[href="${to.path}"]`).addClass('active');});router.beforeEach(async function(to, from, next) { // Fade out current page await _('#app').fadeOut(200); next();});router.afterEach(async function(to, from) { // Fade in new page await _('#app').fadeIn(200);});router.beforeEach(function(to, from, next) { _('#loading').show(); next();});router.afterEach(function(to, from) { _('#loading').hide();});// Create routerconst router = _.createRouter({ mode: 'history', base: '/'});// Define routesrouter.addRoute('/', { name: 'home', component: function() { return ` <h1>Home Page</h1> <p>Welcome to our app!</p> `; }, meta: { title: 'Home' }});router.addRoute('/about', { name: 'about', component: function() { return ` <h1>About Us</h1> <p>Learn more about our company.</p> `; }, meta: { title: 'About' }});router.addRoute('/user/:id', { name: 'user-profile', component: async function(params) { // Fetch user data const user = await _.get(`/api/users/${params.id}`); return ` <h1>${user.name}</h1> <p>Email: ${user.email}</p> <p>Role: ${user.role}</p> `; }, beforeEnter: async function(to, from, next) { // Check if user exists try { await _.get(`/api/users/${to.params.id}`); next(); } catch (error) { next('/404'); } }, meta: { requiresAuth: true, title: 'User Profile' }});router.addRoute('/admin', { name: 'admin', component: function() { return '<h1>Admin Dashboard</h1>'; }, beforeEnter: async function(to, from, next) { const isAdmin = await checkAdminRole(); if (isAdmin) { next(); } else { _.notify('Access denied', 'error'); next('/'); } }, meta: { requiresAuth: true, requiresAdmin: true, title: 'Admin' }});// Global guardsrouter.beforeEach(async function(to, from, next) { // Show loading _('#loading').show(); // Check authentication if (to.meta.requiresAuth) { const isAuth = await checkAuth(); if (!isAuth) { _.notify('Please login first', 'warning'); next('/login'); return; } } next();});router.afterEach(function(to, from) { // Hide loading _('#loading').hide(); // Update page title document.title = to.meta.title + ' - My App'; // Track page view if (typeof gtag !== 'undefined') { gtag('config', 'GA_MEASUREMENT_ID', { page_path: to.path }); } // Scroll to top window.scrollTo(0, 0); // Update active links _('a[data-route]').removeClass('active'); _(`a[href="${to.path}"]`).addClass('active');});// 404 handlerrouter.notFound(function() { return ` <div class="not-found"> <h1>404</h1> <p>Page not found</p> <a href="/" data-route>Go Home</a> </div> `;});// Handle route links_(document).on('click', 'a[data-route]', function(e) { e.preventDefault(); router.navigateTo(_(this).attr('href'));});// Initialize routerrouter.init();const router = _.createRouter({ mode: 'history', base: '/'});// URLs: /home, /about, /user/123const router = _.createRouter({ mode: 'hash'});// URLs: #/home, #/about, #/user/123router.navigateTo(path) // Navigate to pathrouter.navigateTo(route) // Navigate by route objectrouter.back() // Go backrouter.forward() // Go forwardrouter.go(n) // Go n pagesrouter.reload() // Reload current pagerouter.getCurrentRoute() // Get current routerouter.getRoutes() // Get all routesrouter.hasRoute(path) // Check if route existsrouter.getRoute(path) // Get route by pathrouter.getQueryParams() // Get query parametersrouter.addRoute(path, config) // Add routerouter.removeRoute(path) // Remove routerouter.clearRoutes() // Remove all routesrouter.beforeEach(fn) // Global before guardrouter.afterEach(fn) // Global after hookrouter.notFound(fn) // 404 handlerUse named routes for flexibility:
router.navigateTo({ name: 'user', params: { id: 123 } });Validate params in guards:
beforeEnter: async (to, from, next) => { if (!isValidId(to.params.id)) next('/404'); else next();}Handle async operations in guards:
beforeEnter: async (to, from, next) => { await fetchData(); next();}Update document title:
router.afterEach((to) => { document.title = to.meta.title;});Track page views:
router.afterEach((to) => { analytics.track(to.path);});Use loading states:
router.beforeEach((to, from, next) => { _('#loading').show(); next();});Scroll to top on navigation:
router.afterEach(() => { window.scrollTo(0, 0);});