i didn't want to pull in a framework just to swap some content in and out of a div. here's roughly how the router on this site works.

the routes file

navigation is driven by a small routes.json file that lists each top-level page title, slug, and which view module renders it. dynamic sections like /blogs/* and /projects/* use a wildcard slug so a single view can handle every post or project without listing each one by hand.

intercepting navigation

every click on an internal link gets intercepted, preventDefault() is called, and history.pushState swaps the url without a reload. a popstate listener handles the browser's back and forward buttons the same way. classic

matching dynamic paths

for something like /projects/aitji-xyz, the router strips the known prefix and hands whatever's left to the view as a parameter. no regex gymnastics, just split('/') and a bit of patience.

view transitions

i looked at the native view transitions api and decided against it. support is still inconsistent in firefox, and i wanted this to feel the same everywhere. instead there's a small css fade/slide handled with plain class toggles, which works identically in chrome, firefox, and safari.

keeping it honest

no virtual dom, no diffing, no state library. each view is just a function that returns a string of html. it's not going to scale to a huge app, but for a personal site it's plenty, and i can read the entire router in about a minute.