Building This Blog With Zero Dependencies

Building This Blog With Zero Dependencies

Building This Blog With Zero Dependencies

This blog has no framework, no build step, no bundler, and not a single npm package. It's plain HTML, CSS, and JavaScript, served as static files from Cloudflare Pages. Even the Markdown rendering is hand-written.

That wasn't nostalgia. It was a design goal, and working with an AI agent to build it made the constraint easier to hold, not harder.

Why zero dependencies?

Every dependency is a small loan against your future. It can break, change its API, get abandoned, or quietly balloon your bundle. For a blog (text, some images, a bit of interactivity), the interest isn't worth it.

The whole thing needed to do three jobs:

  • Show a list of articles.
  • Render a single article from Markdown.
  • Look good in light and dark mode.

None of that requires React. None of it requires a build pipeline. So it doesn't have one.

How the content works

Articles are just Markdown files in one folder. A single JSON file is the index:

{
  "posts": [
    {
      "slug": "building-this-blog-with-zero-dependencies",
      "title": "Building This Blog With Zero Dependencies",
      "date": "2026-07-18",
      "excerpt": "A static blog with no framework...",
      "tags": ["build-notes", "javascript"],
      "file": "posts/building-this-blog-with-zero-dependencies.md"
    }
  ]
}

Publishing a new post is three steps:

  1. Drop a new .md file in content/posts/.
  2. Add an entry to posts.json.
  3. Upload.

That's the entire content management system. No database, no admin panel, no rebuild.

The part everyone reaches for a library for

Rendering Markdown is where most people npm install marked and move on. But a renderer that covers what a blog actually uses (headings, bold, italics, code, links, images, lists, quotes, tables) is a couple hundred lines of focused code.

The trick that keeps it correct is tokenizing code spans first, so their contents never get reinterpreted as formatting:

// Handle `inline code` before emphasis, so the * inside
// `a * b` is never mistaken for italics.
if (ch === '`') {
  const end = text.indexOf('`', i + 1);
  if (end !== -1) {
    out += '<code>' + escapeHtml(text.slice(i + 1, end)) + '</code>';
    i = end + 1;
    continue;
  }
}

Everything gets HTML-escaped on the way out, so a post can't inject markup it shouldn't. It's small enough to read in one sitting and understand completely, which is exactly the point.

Theming without a flash

Dark mode is a class on the <html> element, toggled by a button and remembered in localStorage. The one subtlety worth getting right is the first paint: a tiny inline script in the <head> sets the theme before the page renders, so there's no flash of the wrong colors.

The best dependency is the one you didn't add. The second best is the one you fully understand.

Where it lives

Cloudflare Pages serves the folder as-is. No build command, no output transform; the files you write are the files that ship. A _headers file sets the right MIME type for .md, and _redirects handles a couple of pretty URLs.

Total moving parts: a handful of HTML pages, three small JS files, one stylesheet, and a folder of Markdown. Fast to load, trivial to maintain, and nothing to keep patched.

That's the whole thing. Sometimes the most satisfying build is the one you didn't over-engineer.