How This Blog Works
Nikos Katsikanis - June 18, 2025
Overview
I call this setup the Vanilla Toolkit: it’s my collection of Vanilla JS patterns that used to fly under the "Vanilla JS Patterns" label. This blog is built entirely with Vanilla JavaScript modules. There are no build tools, no frameworks, and no server-side rendering. Each route is a plain HTML page that loads a client-side router and dynamically imports the content it needs.
Each HTML entry point includes shared layout elements: the <nav> bar, a theme toggle, and a <main data-component="router"> where routes are loaded. See index.html lines 23–54. Scripts like store.js and componentLoader.js are responsible for global state and initializing components.
Page Structure
Client-Side Routing
The router module intercepts link clicks and updates the main view without a full page reload. Paths are normalized using sanitizePath(), removing trailing slashes and index.html so routing logic is consistent. Routes are matched to JS modules and loaded dynamically.
Blog Posts as Modules
Each blog post lives in its own file and exports three things:
date: A JSDateobjecttags: An array of strings like"vanilla-js"content: An HTML string that defines the article
The default export is a function that mounts the content, injects share buttons, loads comment widgets, and updates meta tags for SEO and social previews.
Blog List Rendering
The blog-list component imports posts.js, which defines the blog index. It dynamically imports each blog module and extracts the .preview section if present. It trims out <h1>, dates, and badges to create a clean summary. The previews are capped to 50 words and rendered in a styled list.
This component also enables filtering by tag and sorting by newest/oldest. When browser notifications are allowed, it compares last-post-date in localStorage to the latest blog module and shows a native notification if a new post is available.
Navigation and Sharing
The post-nav component uses the same posts.js array to add "previous" and "next" links on each article. share-buttons.js builds social links from the canonical URL and current title. Comments are handled with the Utterances widget, keyed to the current path.
Meta Tag Updates
update-meta.js updates Open Graph and Twitter cards dynamically when a post is loaded. It uses canonicalUrl() to normalize paths to the /index.html format expected by social crawlers.
Theme and State
A small store.js module implements a global pub/sub pattern. It allows components to listen for shared state changes using plain custom events. The theme switcher toggles a data-theme attribute on the <html> element and persists the choice in localStorage.
In Short
This blog demonstrates how far you can go with native browser features and modular design. There’s no build step, no bundler, and no framework—just components, modules, and a tiny router. It’s fast to load, easy to reason about, and fully transparent to inspect and extend.