← All essays
Front-end8 min read

A Static Site That Still Feels Alive

No framework, no CDN, no trackers. How a handful of HTML files, one stylesheet and one script can still deliver search, filtering and fluid navigation.

Abstract cover artwork for this essay

This journal has no build framework, no package manager, no node_modules, no analytics, no font CDN, and no cookie banner. It is a directory of HTML files, one stylesheet, two small scripts, and some SVG. It still has instant search, category filtering, a table of contents, theme switching, and animated page transitions.

I wanted to find out how much perceived interactivity is actually just HTML and CSS with a thin layer of JavaScript on top. The answer: almost all of it.

The file tree

index.html  about.html  categories.html  tags.html  404.html
posts/*.html            <- one file per essay
assets/css/styles.css   <- every visual rule
assets/js/site.js       <- behaviour
assets/js/posts.js      <- generated metadata index
assets/img/*.svg        <- locally generated artwork
robots.txt
tools/                  <- local generator and preview server

There is a small Python generator in tools/, but it is a convenience for keeping the shared header and footer consistent — not a runtime dependency. The files it writes are ordinary, complete HTML documents. You can open them directly from disk and they work.

Progressive enhancement as architecture

The rule I followed: if JavaScript never runs, every page must still be fully readable and every link must still work. That means article lists are real markup, not client-rendered JSON. Search, filtering, and transitions are added afterwards, on top of a complete document.

Search without a service

A generated metadata array of six essays is small enough to ship with the page. Filtering it in memory on each keystroke is imperceptibly fast and works offline. There is no search provider, no API key, and no third-party request leaking reading behaviour.

const matches = window.BLOG_POSTS.filter(post =>
  (post.title + post.excerpt + post.tags.join(' '))
    .toLowerCase().includes(query.toLowerCase())
);

The same array powers related essays and the command-palette style overlay. One data file, three features, zero network calls.

The performance budget

  • Zero external origins. No fonts, icons, scripts, or images are fetched from anywhere else.
  • One stylesheet, one behaviour script, both deferred or cached aggressively.
  • All decorative artwork is SVG or CSS gradients; photographs are avoided entirely.
  • Only transform and opacity are animated on scrolling elements.
  • content-visibility and lazy loading keep long pages cheap.

What I gave up

There is no comment system, no server-side full-text index, and no admin UI. Writing a new essay means adding an entry to a Python data file and running one command. For a personal journal that is a feature, not a limitation: the surface area is small enough that I can still understand every byte I ship.

The most reliable dependency is the one you never added.

The reason this site has no package.json

Static does not have to mean inert. With a clear separation between content, presentation, and a thin behaviour layer, a directory of files can feel as considered as any application — and it will still load in 2036.