Skip to content

Instantly share code, notes, and snippets.

@robert-hoffmann
Created July 4, 2025 20:56
Show Gist options
  • Select an option

  • Save robert-hoffmann/48ae4a5b112af5181abab5da8e37dabf to your computer and use it in GitHub Desktop.

Select an option

Save robert-hoffmann/48ae4a5b112af5181abab5da8e37dabf to your computer and use it in GitHub Desktop.
VanillaJS (VueRouter style) Router
const MyRouter = (function(win) {
// Create a safe default route object
const createSafeRoute = (routeData = null) => {
if (!routeData) {
return {
query : {},
path : "",
params: {}
};
}
return {
query : routeData.query || {},
path : routeData.path || "",
params: routeData.params || {}
};
};
let currentRoute = createSafeRoute();
let previousRoute = createSafeRoute();
let routeHandler = null;
function handleRoute(e) {
const newRoute = e.detail;
// Update route history with safe objects
previousRoute = createSafeRoute(currentRoute);
currentRoute = createSafeRoute(newRoute);
// Call handler with Vue-style (to, from) pattern
if (routeHandler) {
routeHandler(currentRoute, previousRoute);
} else {
console.log('Route changed:', { to: currentRoute, from: previousRoute });
}
}
win.addEventListener("routeEvent", handleRoute);
// Constants - declared once
const PLUS_REGEX = /\+/g;
const PARAM_REGEX = /([^&=]+)=?([^&]*)/g;
// Utility functions
const decode = (s) => {
try {
return decodeURIComponent(s.replace(PLUS_REGEX, " "));
} catch (e) {
console.warn('Failed to decode URL parameter:', s, e);
return s; // Return original string if decode fails
}
};
const parseParams = (queryString) => {
const params = {};
if (!queryString) return params;
const matches = queryString.matchAll(PARAM_REGEX);
for (const match of matches) {
const key = decode(match[1]);
const value = decode(match[2]);
params[key] = value; // Always add - decode never returns null
}
return params;
};
const parseRoute = () => {
const query = win.location.search.substring(1);
const hash = win.location.hash.slice(1);
const hashQueryIndex = hash.indexOf("?");
let path = hash;
let hashParams = {};
// Handle hash with query parameters
if (hashQueryIndex !== -1) {
path = hash.substring(0, hashQueryIndex);
const hashQuery = hash.substring(hashQueryIndex + 1);
hashParams = parseParams(hashQuery);
}
return {
query : parseParams(query),
path : path,
params: hashQueryIndex === -1 ? parseParams(query) : hashParams
};
};
const handleRouteChange = () => {
const router = parseRoute();
win.dispatchEvent(new CustomEvent("routeEvent", { detail: router }));
};
// Set up event listener
win.addEventListener('popstate', handleRouteChange);
// Initial route parsing
handleRouteChange();
return {
// Vue Router style method
beforeEach: (handler) => {
if (typeof handler === 'function') {
routeHandler = handler;
}
},
// Navigation methods (Vue Router style)
push: (path) => {
win.location.hash = path;
},
replace: (path) => {
win.location.replace(win.location.pathname + win.location.search + '#' + path);
},
// Always returns safe {to, from} object
getRouteState: () => ({
to : createSafeRoute(currentRoute),
from: createSafeRoute(previousRoute)
}),
// Returns just current route
currentRoute: () => createSafeRoute(currentRoute),
// Returns just previous route
previousRoute: () => createSafeRoute(previousRoute),
// Go back/forward
go: (n) => {
win.history.go(n);
},
back: () => {
win.history.back();
},
forward: () => {
win.history.forward();
}
};
})(window);
@robert-hoffmann
Copy link
Author

This is actually a old router i made, but decided to make do some brainstorming with Sonnet 4, and see if i can make it better:

So now it has behavior like VueRouter: beforeEach(to, from), and also various methods

You can see the full docs here:
https://claude.ai/public/artifacts/c1ea2981-b655-4366-ba3e-1f669cf43d52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment