Simple SPA router with smooth transitions, component loading, and Shadow DOM support
npm install @profullstack/spa-routerA lightweight, feature-rich SPA router with smooth transitions and Shadow DOM support.
- Simple API for defining routes
- Support for dynamic routes with parameters
- Smooth page transitions with customizable effects
- Shadow DOM support for intercepting clicks
- History API integration
- Route guards and middleware
- Lazy loading support
- Web component integration
- Enhanced renderer with component preservation and translation support
``bash`
npm install @profullstack/spa-router
Or with yarn:
`bash`
yarn add @profullstack/spa-router
For browser environments, you can use CDNs to import the router directly without npm:
`html`
`html`
`javascript
// Using npm package
import { Router, transitions } from '@profullstack/spa-router';
// Or using CDN
// import { Router, transitions } from 'https://esm.sh/@profullstack/spa-router@1.5.0';
// Or using jsDelivr
// import { Router, transitions } from 'https://cdn.jsdelivr.net/npm/@profullstack/spa-router@1.5.0/dist/index.esm.js';
// Initialize router
const router = new Router({
rootElement: '#app',
transition: transitions.fade({ duration: 150 })
});
// Define routes
router.registerRoutes({
'/': {
view: () => '
}
});// Programmatic navigation
document.querySelector('#nav-about').addEventListener('click', () => {
router.navigate('/about');
});
`Advanced Usage
$3
`javascript
import { Router, transitions } from '@profullstack/spa-router';const router = new Router({
rootElement: '#app',
transition: transitions.slide({ direction: 'left', duration: 300 })
});
`$3
The router includes an enhanced renderer that supports component preservation and translations:
`javascript
import { Router, transitions, renderer } from '@profullstack/spa-router';// Create a custom renderer with translation support
const customRenderer = renderer.createRenderer({
// Function to translate a container
translateContainer: (container) => {
// Your translation logic here
container.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = translate(key);
});
},
// Function to apply RTL direction to document
applyRTLToDocument: () => {
// Your RTL logic here
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
}
});
const router = new Router({
rootElement: '#app',
transition: transitions.fade({ duration: 300 }),
renderer: customRenderer
});
`$3
`javascript
router.registerRoutes({
'/admin': {
view: () => 'Admin Dashboard
',
beforeEnter: (to, from, next) => {
if (!isAuthenticated()) {
return next('/login');
}
next();
}
}
});
`$3
`javascript
router.use(async (to, from, next) => {
// Log all navigation
console.log(Navigating from ${from} to ${to.path});
next();
});
`$3
`javascript
router.registerRoutes({
'/dashboard': {
component: () => import('./components/dashboard.js')
}
});
`$3
`javascript
router.registerRoutes({
'/api-keys': {
component: 'api-key-manager',
props: { apiVersion: 'v1' }
}
});
`Server-Side Configuration
For SPA routing to work properly with direct URL access, you need to configure your server to redirect all requests to your index.html file. Here are examples for popular Node.js frameworks:
$3
`javascript
import { Hono } from 'hono';
import { serveStatic } from 'hono/serve-static';
import { join } from 'path';const app = new Hono();
// Serve static files from the 'public' directory
app.use('/*', serveStatic({ root: './public' }));
// SPA fallback - redirect all unmatched routes to index.html
app.get('*', async (c) => {
// Check if the request is for a file with extension
const path = c.req.path;
if (path.match(/\.\w+$/)) {
// Let the static middleware handle it
return c.next();
}
// Otherwise serve index.html for SPA routing
return c.html(
await Bun.file(join(process.cwd(), 'public', 'index.html')).text()
);
});
export default app;
`$3
`javascript
import express from 'express';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';// Get current file directory with ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
// Serve static files from the 'public' directory
app.use(express.static(join(__dirname, 'public')));
// SPA fallback - redirect all unmatched routes to index.html
app.get('*', (req, res) => {
// Check if the request is for a file with extension
if (req.path.match(/\.\w+$/)) {
// Let Express handle 404 for missing files
return res.status(404).send('Not found');
}
// Otherwise serve index.html for SPA routing
res.sendFile(join(__dirname, 'public', 'index.html'));
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(
Server running on port ${PORT});
});
`$3
`javascript
import Koa from 'koa';
import serve from 'koa-static';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { createReadStream } from 'fs';// Get current file directory with ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = new Koa();
// Serve static files from the 'public' directory
app.use(serve(join(__dirname, 'public')));
// SPA fallback - redirect all unmatched routes to index.html
app.use(async (ctx) => {
// Check if the request is for a file with extension
if (ctx.path.match(/\.\w+$/)) {
// Let koa-static handle 404 for missing files
return;
}
// Otherwise serve index.html for SPA routing
ctx.type = 'html';
ctx.body = createReadStream(join(__dirname, 'public', 'index.html'));
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(
Server running on port ${PORT});
});
`API Reference
$3
#### Constructor Options
-
rootElement: CSS selector for the root element where content will be rendered
- transition: Transition effect to use for page changes
- renderer: Custom renderer function for content rendering
- errorHandler: Custom 404 error handler#### Methods
-
navigate(path, pushState = true): Navigate to a path
- registerRoutes(routes): Register multiple routes
- addRoute(path, routeConfig): Add a single route
- removeRoute(path): Remove a route
- getCurrentRoute(): Get the current route
- back(): Go back in history
- forward(): Go forward in history
- use(middleware): Add middleware$3
-
fade(options): Fade transition
- slide(options): Slide transition
- none(): No transition
- custom(fn): Custom transition function$3
-
createRenderer(options): Create a custom renderer with component preservation and translation support
- createErrorHandler(options)`: Create a custom error handlerMIT