Building a Blog in 90 Minutes with 233 LOC

I just built this blog from scratch in about 90 minutes. It's exactly 233 LOC of build code, deploys with one command, and will probably outlive your Medium posts. Actually, let me rephrase that - it'll outlive Medium as a platform.

Magic 8-Ball Development

I did it using Claude Code.

My humble contribution to the agentic engineering discourse is something I don't see talked about a lot: agentic engineering speeds up development so much on greenfield projects that you can avoid pulling in tons of dependencies. Instead of reaching for a framework because building from scratch would take too long, you can build exactly what you need in the time it would take to configure someone else's solution.

Maybe it's a naive approach. Maybe it won't scale. But honestly, that's the point. You're speed-running the iterative development cycle that platforms go through over decades - but only building what you actually want. No advertising considerations, no investor demands, no feature creep from committee decisions. Just the refined solution that serves your actual needs.

There's something intoxicating about starting from a blank terminal and describing what you want: "a minimal blog that renders fast and will work forever." No starter templates, no boilerplate, no inherited decisions. Just complete control - every line of code exists for a reason, every decision intentional. No mystery meat in the node_modules folder, no configuration you're afraid to touch, no build process you don't understand.

When you build from scratch with an AI pair programmer, you're not fighting the tool - you're having a conversation about what you actually need. And it turns out, for a blog, you need surprisingly little.

The Constraints

My goal was to build a brutalist, hyper-minimalist blog where every element serves a purpose. I started with several clear opinions:

The Stack

{
  "build": "node (233 LOC)",
  "parse": "marked (17kb)",
  "highlight": "prism (15kb)",
  "deploy": "rsync over SSH",
  "cdn": "none",
  "bundler": "none",
  "framework": "none",
  "hosting": "DigitalOcean ($6/month)",
  "server": "nginx on Ubuntu"
}

That's it. The entire build script is shorter than most webpack configs. It runs on a vanilla Linux server - just nginx serving static files from a DigitalOcean droplet. No containers, no orchestration, no managed services. HTML files on a hard drive, the way the web started.

How It Works

Write markdown files with frontmatter in src/posts/:

---
title: Your Post Title
date: 2024-08-16
description: Brief description
---

Your content here...

Run build:

npm run build  # Generates static HTML

Deploy:

npm run deploy  # rsync to your server

The build script:

  1. Reads markdown files
  2. Parses frontmatter
  3. Converts to HTML with syntax highlighting
  4. Injects into a template
  5. Writes static files

No hot reloading, no webpack, no babel, no virtual DOM. Just files.

Interactive Examples (When Needed)

While the blog is static, you can embed JavaScript directly in markdown when you actually need interactivity:

0

This works because Markdown passes HTML through untouched.

The Philosophy

Durability over features. This blog will work in 20 years.

Speed over developer experience. Pages load in 50ms. No JavaScript required.

Ownership over convenience. My server, my domain, my rules. No platform risk.

Simplicity over scalability. It's a blog, not Facebook. It doesn't need to scale.

The Numbers

Why Not [Insert Platform]?

Medium/Substack/Ghost: Platform risk. They own your audience.

Gatsby/Next.js: 500MB of node_modules to generate HTML.

Jekyll/Hugo: Fine choices, but even they're overkill for a blog. It gets back to the control I was talking about earlier.

WordPress: It's 2025.

The Code

The entire build script fits in a single file. Here's the core:

// Read all posts
const posts = readdirSync('./src/posts')
  .filter(f => f.endsWith('.md'))
  .map(parsePost);

// Generate HTML
posts.forEach(post => {
  const html = marked(post.content);
  const output = template
    .replace('{{title}}', post.title)
    .replace('{{content}}', html);
  writeFileSync(`public/${post.slug}/index.html`, output);
});

// Generate index
const index = indexTemplate
  .replace('{{posts}}', posts.map(p => 
    `<a href="/${p.slug}/">${p.title}</a>`
  ).join(''));
writeFileSync('public/index.html', index);

That's basically it.

Will It Scale?

It's static HTML. It scales infinitely. But more importantly: it's a personal blog. It doesn't need to scale. I'm not building the next Twitter here.

The Source

Fork this blog on GitHub

Feel free to fork it. The entire thing is MIT licensed and documented. You can have your own blog running in 90 minutes too.

The Point

We've forgotten that websites can be simple. A blog doesn't need a framework. It doesn't need a build pipeline. It doesn't need to be a single-page application.

Sometimes the best technical decision is to not make one. HTML is already a pretty good document format. Browsers are already pretty good at displaying it.

The web is good at being the web. Let it.