Building a Lightning-Fast Static Site With Zero Frameworks

By Λ · May 18, 2026 · 9 min read

BoltQuickTools has over a hundred pages, ten blog posts, half a dozen category indexes, multiple language layers, and a sitemap. It uses zero JavaScript frameworks. No React, no Vue, no Next, no Astro, no Gatsby, no SvelteKit. There is no build step that takes more than two seconds. This essay is the story of why I chose this stack, what I gave up, and what I would do differently if I started over.

The stack in one paragraph

nginx (in a Docker container) serves static HTML, CSS, and JavaScript files behind Cloudflare. The HTML files are mostly hand-written, with a Python script that generates the homepage, category indexes, sitemap, and blog index from a YAML manifest. There is no JavaScript bundler. There is no transpilation. There is no virtual DOM. Total dependency count for the build pipeline: Python 3 and PyYAML.

Why no framework

I have built things with React for years. I like React. I would use React again for an app where state is rich, where you have many components that share state, and where the user interacts intensely with one page over a long session. None of those properties apply to BoltQuickTools.

Each tool page is a single-purpose interaction with shallow state. A JSON formatter has two textareas and four buttons. A QR code generator has one input and one canvas. These do not benefit from component composition; they benefit from being a tiny self-contained HTML file that loads in 200 ms.

The framework cost is also higher than people remember. A minimal React app ships ~45 KB of runtime even before your code. A Next.js app brings the framework, the router, and a hydration layer, usually ~120 KB of JavaScript on first load. For tool pages that ship 60 KB total today, that is a 2x weight penalty paid on every page load for zero functional gain.

The single-HTML-file pattern

Every tool page is structured as one HTML file containing:

Total file size per tool: 20 to 60 KB. Gzipped, 8 to 20 KB. That is the entire payload. The first paint on a fresh visit is the time to download one HTML file plus the shared CSS, which is in the Cloudflare edge cache. Subsequent visits are even faster because the CSS is cached locally.

Templating without a framework

I do not write each page's nav and footer by hand for every tool. That would be a maintenance nightmare. Instead, a Python script (build_site.py) reads a YAML manifest of tools and generates the homepage tool grid, the category index pages, the sitemap, and the OG image metadata. The actual tool body is hand-written and not regenerated.

This split mirrors the real change frequency: navigation and category structure changes often, tool logic does not. Regenerating navigation is cheap. Regenerating tool logic is impossible (the script does not know how a hash generator works).

# Tool manifest snippet
tools:
  - slug: json-formatter
    name: JSON Formatter
    category: code-tools
    description: Format, beautify, minify, validate JSON
    icon: "{ }"
    keywords: [json, format, validate, beautify, minify]

Total build time: under 2 seconds for the whole site. No watcher, no hot reload, no incremental rebuilds. python3 build_site.py && docker compose up -d web is the entire deploy pipeline.

CSS without a preprocessor

There is one style.css file, ~500 lines, using CSS custom properties for theming. No Sass, no PostCSS, no Tailwind. The trade-offs:

What I gave up: Tailwind's utility classes. Component-scoped styles. The ergonomics of $variable nesting from Sass.

What I gained: No build dependency. The CSS file I write is the CSS file the browser receives. No source maps, no debug-vs-prod confusion. CSS custom properties give me 90% of the variable ergonomics for none of the build cost.

JavaScript without a bundler

Each tool ships its own inline JavaScript, scoped to the tool page. Shared utilities (cookie consent, language switcher, ad-slot collapser) live in /assets/*.js files and are loaded via plain <script> tags. There are no modules, no imports, no exports. ES2020 syntax used directly.

If two tools share a utility (say, a download-as-file helper), I either inline it in both or refactor it into a shared file. The decision is made per-helper. Some live in three files because the cost of a shared file (extra HTTP request) is higher than the cost of 40 lines of duplication.

Where this falls down

The pattern is not universal. Three failure modes I have hit:

1. Cross-page state

The "recently used tools" feature on the homepage needs to know which tools you used. I keep it in localStorage and read it on the homepage. Works fine. But if I wanted "recently used tools across devices," I would need a backend and an account system, which destroys the privacy model. So I do not build that feature.

2. Complex shared UI

If I had a complex form that appears on twenty pages, copy-pasting the markup would become a real maintenance burden. So far, the only "shared component" is the nav and footer, which the Python script handles. Anything more complex would push me toward a templating engine. I have not hit that limit.

3. Interactive state machines

The randomizer tools have a small state machine (idle, rolling, locked, undone). Implementing it in vanilla JS with explicit state is fine for one state machine. If I had ten tools that all needed similar interaction, I would consider refactoring to a small shared state library. So far, only the gaming randomizers fit that pattern, and they share a randomizer.js helper.

The Lighthouse scorecard

The tool pages typically score Performance 95+, Accessibility 95+, Best Practices 92+, SEO 100. The Performance ceiling is set by the AdSense scripts, which I have no control over. Without ads, the same pages score 99 to 100 on Performance.

This is the practical demonstration: there is no need to ship megabytes of framework code to get a fast site. Static HTML, hand-written JS, and one CSS file is enough.

What I would change

If I started over knowing what I know now:

What I would NOT change

The no-framework decision. The static-file architecture. The Python build script. The single-HTML-file-per-tool pattern. These are the choices that make the site fast, cheap, and maintainable by one person.

If you are building a personal tool site, a portfolio, a documentation site, or anything with shallow per-page interaction, please consider not reaching for a framework. The web platform is more capable than the framework discourse implies. You can build a fast, privacy-respecting, search-indexable site with the same set of tools we had in 2015, and the result will be smaller, faster, and easier to maintain than the equivalent built with the current framework du jour.

Related