Every week someone ships a new blog on Next.js with a Vercel pipeline, a headless CMS, an ORM talking to a database that stores… text. Four hundred kilobytes of client-side JavaScript, a deploy that takes ninety seconds, a node_modules folder heavier than the actual writing — all to render paragraphs on a screen.
I wanted to see what happens when you go the other direction entirely.
This site builds in 300 milliseconds. There is no JavaScript framework. The dependency list fits in a tweet. Here’s the whole thing.
The Stack
| Layer | Tool | Why |
|---|---|---|
| Generator | Jekyll | Ruby, battle-tested, boring in the best way |
| Base theme | Chirpy | Stripped to the studs and rebuilt |
| Styles | Sass | Compiles at build time, browser never knows |
| Syntax highlighting | Rouge | Happens during build, not at runtime |
| Hosting | GitHub Pages | It’s just files on a CDN |
The entire dependency tree lives in a Gemfile with three lines. No package.json. No node_modules. No lockfile conflicts at 2 AM.
Why No Framework
Let’s be honest about what a blog actually is. Someone writes. Someone reads. That’s the interaction model. There is no complex state to manage, no real-time data to sync, no interactive widget that justifies shipping a virtual DOM to the browser.
React solves real problems — in applications. A blog isn’t an application. It’s a document. And browsers have been rendering documents since 1993. They’re extraordinarily good at it, if you let them.
When you load this site, your browser receives a complete HTML file. Everything is already there. No API calls to fetch content that’s sitting three feet away in the same repo. No skeleton screens. No hydration pass where JavaScript re-discovers what the server already knew. The page is done before your finger leaves the trackpad.
Jekyll’s build is almost comically simple: read Markdown, apply a template, compile Sass, write HTML. Three hundred milliseconds for the whole site. There’s nothing to debug because there’s barely anything running.
Why Sass Over Tailwind
Tailwind is clever, I’ll give it that. But it solves a coordination problem — keeping styles consistent across a large team working on a large app. A personal blog with one author and one stylesheet doesn’t have that problem.
What Tailwind does need: Node.js, a build pipeline, a purge step to remove the 99% of generated CSS you didn’t use, and markup that ends up looking like this:
1
2
<div class="bg-gray-800/50 border border-white/10 rounded-2xl
transition-colors hover:bg-gray-700/60 hover:border-white/20">
You’re encoding your entire design system in class strings scattered across every template. Change direction on a design decision and you’re grepping through dozens of files.
Sass compiles to plain CSS. The browser never sees it — just a normal .css file, cached and done. I keep the whole design system in CSS custom properties:
1
2
3
4
5
6
7
:root {
--glass-bg: rgba(255, 255, 255, 0.06);
--glass-border: rgba(255, 255, 255, 0.08);
--card-bg: rgba(28, 30, 38, 0.52);
--font-display: "Space Grotesk", sans-serif;
--font-body: "Manrope", sans-serif;
}
Want to change the mood of the entire site? Edit five lines. Dark mode to light mode is a variable swap under html[data-mode=light]. No theming library. No context providers. No JavaScript involved at all.
The Glassmorphic Design
The visual style of this site — frosted glass surfaces, soft depth, grain textures — is built entirely with vanilla CSS. No UI library. No component kit. No design framework. Just Sass compiled to a single stylesheet.
The core of it is embarrassingly simple. Every glass surface on the site is built from the same handful of properties:
1
2
3
4
5
6
7
8
9
10
11
.card {
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.07) 0%,
rgba(255, 255, 255, 0.02) 100%
);
border: 1px solid rgba(255, 255, 255, 0.1);
border-top-color: rgba(255, 255, 255, 0.15);
border-radius: 16px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16);
}
A top-to-bottom gradient with barely-there white opacity. A border that catches light on the top edge. A subtle shadow for depth. That’s the entire glass effect. No backdrop-filter on most elements — I found that blurring the background behind every card murdered performance on older hardware and didn’t look noticeably different from a well-tuned gradient.
The grain texture is an inline SVG fed to background-image through a ::after pseudo-element:
1
2
3
4
5
6
7
8
9
.card::after {
content: "";
position: absolute;
inset: 0;
opacity: 0.12;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,...feTurbulence...");
background-size: 128px 128px;
}
It’s a feTurbulence filter baked into a tiny SVG — no image file to load, no network request, renders instantly. Every card, every code block, every row on the site uses the same texture at different opacities. That’s what gives the surface its tactile quality instead of feeling like flat coloured rectangles.
Light mode is the same system with the values flipped. Where dark mode uses white at low opacity, light mode uses higher opacity whites against a warm #f4f0e8 base. The entire theme swap is CSS custom properties — one set of variables for dark, one override block for light. The browser handles the rest.
The floating header uses backdrop-filter: blur(20px) — one of the few places I actually blur — because it sits over scrolling content and the effect is worth the cost there. Everything else fakes the glass look with gradients and borders, which costs essentially nothing to render.
The whole design system lives in one Sass file. No Figma tokens pipeline. No styled-components. No CSS-in-JS runtime. One file, a few hundred lines of variables and mixins, and the browser does what browsers do best — paint pixels fast.
How Content Works
Every post is a Markdown file with a few lines of metadata at the top:
1
2
3
4
5
6
---
title: How This Blog Works, Technically
categories: [engineering]
tags: [jekyll, static-sites, sass]
author: EXE
---
Write in any text editor. Commit to Git. Jekyll builds it into HTML — a folder of static files you can host anywhere. GitHub Pages, Cloudflare, an S3 bucket, a Raspberry Pi in your closet. There is no vendor to lock you in because there is no vendor. It’s just files.
If GitHub Pages disappeared tomorrow, I’d move the entire site in twenty minutes. Try saying that about your Contentful + Vercel + PlanetScale stack.
The Tradeoffs
Nothing is free, and I don’t want to pretend this approach has no cost. Here’s what I gave up and what I got back.
What I gained:
- 300ms builds for the full site — fast enough that I rebuild on every save without thinking about it
- Zero JavaScript frameworks — a couple hundred lines of vanilla JS handle search and the theme toggle, that’s it
- Perfect Lighthouse scores — not because I optimised hard, but because there was nothing to slow it down in the first place
- No dependency hell — no
npm auditwarnings, no breaking updates, no morning surprises - Total design control — every pixel, every animation, every grain texture is hand-placed
What I gave up:
- No CMS dashboard — I write in a text editor and commit through Git
- No real-time features — no comments, no notifications, no feeds
- No dynamic content — changes need a rebuild, which takes 300ms, so I’m not losing sleep
Honestly, if you live in a terminal, none of these feel like losses.
The Point
The web has a complexity addiction. Somewhere along the way we normalised reaching for the heaviest tools first, regardless of whether the job called for them. Need a blog? Better set up a React project with server components and a database. Need to show some text? Ship a bundler.
A blog is a simple job. It deserves simple tools.
This site loads instantly, costs nothing to host, and will still work ten years from now without a single dependency update. No package will go unmaintained. No API will sunset. No company will pivot and break my deploy pipeline.
Sometimes the most radical engineering decision is choosing not to add more technology.