A minimal, dependency-free client-side router for modern web applications with built-in caching, script execution, and SSR compatibility.
- Ultra lightweight - Single file, no dependencies
- Automatic link interception - Works with regular
<a>tags - Route parameters - Support for
:idparams and*wildcards - Page caching - Automatic caching of fetched pages
- Intelligent prefetch - Automatic prefetching on hover with configurable delay
- Script execution - Runs scripts from dynamically loaded content
- SSR friendly - Works seamlessly with server-side rendered pages
- Flexible rendering - Custom or automatic content rendering
- Modern ES modules - Native module support
npm install teeny-tiny-router<script type="module">
import { MiniRouter } from "https://unpkg.com/teeny-tiny-router/dist/teeny-tiny-router.es.js";
</script><!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/posts/123">Post 123</a>
</nav>
<div id="app"></div>
<script type="module">
import { MiniRouter } from "./src/index.js";
const router = new MiniRouter();
// Handle navigation events
router.on("navigate", ({ title, body }) => {
if (title) document.title = title;
document.querySelector("#app").innerHTML = body;
});
// Define routes
router.route("/", () => {
console.log("Home page loaded");
});
router.route("/posts/:id", (params) => {
console.log("Post ID:", params.id);
});
// Initialize current page
router.navigate(location.pathname, { replace: true });
</script>
</body>
</html>const router = new MiniRouter({
htmlExtension: true, // Add .html to URLs
interceptAllLinks: true, // Auto-intercept internal links
contentSelector: "#app", // CSS selector to extract content from fetched pages
prefetchOnHover: true, // Enable automatic prefetch on hover
prefetchDelay: 0, // Delay in ms before prefetch starts
});htmlExtension(boolean, default:true) - Automatically adds.htmlextension to URLs that don't have oneinterceptAllLinks(boolean, default:true) - Automatically intercepts all internal links. Set tofalseto use opt-in mode withdata-linkattributecontentSelector(string, default:'#app') - CSS selector used to extract content from fetched HTML pages. If element not found, falls back to<body>contentprefetchOnHover(boolean, default:true) - Automatically prefetch pages when hovering over linksprefetchDelay(number, default:0) - Delay in milliseconds before starting prefetch on hover. Set to 0 for instant prefetch, or a higher value (e.g., 100-300ms) to avoid prefetching during quick mouse movements
Define a route handler.
// Simple route
router.route("/about", () => {
console.log("About page");
});
// Route with parameters
router.route("/users/:id", (params) => {
console.log("User ID:", params.id);
});
// Wildcard route
router.route("/admin/*", (params) => {
console.log("Admin path:", params["*"]);
});
// Override content from route
router.route("/custom", () => {
return {
title: "Custom Page",
body: "<h1>Custom Content</h1>",
};
});Programmatically navigate to a URL.
// Navigate to a new page
router.navigate("/about");
// Replace current history entry
router.navigate("/about", { replace: true });Listen to router events.
// Navigation events
router.on("navigate", ({ url, title, body }) => {
document.title = title;
document.querySelector("#app").innerHTML = body;
});
// Route-specific events
router.on("route:/users/:id", ({ params, url, title, body }) => {
console.log("User route matched:", params.id);
});
// Prefetch events
router.on("prefetch", ({ url, data }) => {
console.log("Page prefetched:", url);
});
router.on("prefetch:error", ({ url, error }) => {
console.log("Prefetch failed:", url, error);
});Manually prefetch a page in the background.
// Prefetch a single page
router.prefetch("/about");Prefetch multiple pages at once.
// Prefetch multiple pages
router.prefetchAll(["/about", "/contact", "/services"]);Clear the page cache.
// Clear specific page from cache
router.clearCache("/about");
// Clear entire cache
router.clearCache();Get cache statistics.
const info = router.getCacheInfo();
console.log(`Cache size: ${info.size}`);
console.log(`Cached URLs:`, info.urls);Enable or disable prefetch on hover.
// Disable prefetch on hover
router.setPrefetchOnHover(false);
// Re-enable prefetch on hover
router.setPrefetchOnHover(true);// Named parameters
router.route("/users/:id/posts/:postId", (params) => {
console.log(params.id); // user ID
console.log(params.postId); // post ID
});
// Wildcard (catch-all)
router.route("/files/*", (params) => {
console.log(params["*"]); // everything after /files/
});Route handlers can return content to override the fetched page:
// Return HTML string
router.route("/dynamic", () => {
return "<h1>Dynamic Content</h1>";
});
// Return object with title and body
router.route("/custom", () => {
return {
title: "Custom Title",
body: "<h1>Custom Body</h1>",
};
});By default, the router automatically prefetches pages when users hover over links, significantly improving navigation speed. Prefetch happens instantly (no delay) to maximize the chance of having content ready before the user clicks.
You can manually prefetch pages to warm up the cache:
// Prefetch important pages on app startup
router.prefetchAll(["/about", "/contact", "/products"]);
// Prefetch based on user behavior
document.addEventListener("scroll", () => {
router.prefetch("/next-section");
});Use the data-no-prefetch attribute to prevent automatic prefetching:
<!-- This link won't be prefetched on hover -->
<a href="/heavy-page" data-no-prefetch>Heavy Page</a>
<!-- External links are automatically excluded -->
<a href="https://external-site.com">External</a>
<!-- Download links are automatically excluded -->
<a href="/document.pdf" download>Download PDF</a>Monitor prefetch activity with events:
router.on("prefetch", ({ url, data }) => {
console.log(`✓ Prefetched: ${url}`);
});
router.on("prefetch:error", ({ url, error }) => {
console.log(`✗ Prefetch failed: ${url}`);
});Monitor and manage the page cache:
// Check cache status
const { size, urls } = router.getCacheInfo();
console.log(`${size} pages cached: ${urls.join(", ")}`);
// Clear cache when needed
if (size > 50) {
router.clearCache(); // Clear all
}
// Or clear specific pages
router.clearCache("/old-page");// main.go
func aboutHandler(w http.ResponseWriter, r *http.Request) {
tmpl := `
<!DOCTYPE html>
<html>
<head>
<title>About Us</title>
</head>
<body>
<div id="app">
<h1>About Page</h1>
<p>Server-rendered content</p>
<script>
console.log('Page-specific script executed');
</script>
</div>
</body>
</html>`
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, tmpl)
}app.get("/about", (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>About</title>
</head>
<body>
<div id="app">
<h1>About Page</h1>
<script>console.log('About page loaded');</script>
</div>
</body>
</html>
`);
});Scripts in loaded content are automatically executed:
import { MiniRouter, executeScripts } from "./src/index.js";
router.on("navigate", ({ title, body }) => {
if (title) document.title = title;
const app = document.querySelector("#app");
app.innerHTML = body;
executeScripts(app); // Manually execute scripts if needed
});// Disable automatic link interception
const router = new MiniRouter({ interceptAllLinks: false });
// Use data-link attribute for specific links
<a href="/about" data-link>
About
</a>;- Chrome 61+
- Firefox 60+
- Safari 10.1+
- Edge 16+
Requires ES2017+ support (async/await, modules).
- Fork the repository
- Create your feature branch
- Add tests if applicable
- Submit a pull request
MIT License - see LICENSE file for details.