Back to Hub
ENGINEERING •
By Yash Panchal · Last updated:

I Built a Blog Engine With No Build Step | NitroIDE

There's no CMS behind this blog. No static site generator, no build step, no database. Thirty-seven posts, and the entire "engine" is five files plus a checklist.

That's not a brag — it's a design I can explain in one sitting, and if you run a docs site or a personal blog on a static host, you can steal the whole pattern today.

The five files

One JSON file is the brain. blog/blog-data.json holds one object per post — slug, meta title and description, keywords, category, date, an HTML excerpt, a per-post theme color, and per-post CTA fields. Thirty-seven entries. Every list on the site renders from this file.

Each post is its own HTML page. blog/<slug>.html — fully self-contained, with its own meta tags and OG tags. No templates rendered server-side, no build injecting headers. Open view-source on any post and you're looking at the whole thing.

The index renders client-side. blog/index.html fetches blog-data.json, dedupes entries by slug (a line that exists because duplicates have genuinely happened), and renders the card grid. One fetch, one file, no framework.

RSS is a hand-edited file with a gate in front of it. rss.xml carries all 37 items, maintained by hand. The comment at the top of the file says it plainly: before committing, run python3 -c "import xml.dom.minidom; xml.dom.minidom.parse('rss.xml')" — it must exit 0. Then a second check counts <item> opens against closes, because the gate comment itself mentions <item> and would corrupt a naive count. This gate exists because the live feed broke three times — Sep 2026, Oct 8, Oct 10 — a missing closing tag every time. Automation doesn't have to be clever. It has to remember the last three times you broke it.

The sitemap gets honest dates from git. Eighty-six URLs in sitemap.xml, each <lastmod> derived from git log -1 --format=%cs -- <file>. I wrote the full recipe for that one separately — the short version is: a sitemap that claims everything changed today teaches crawlers to stop trusting it.

The decision I'm proudest of: the noscript list

JavaScript renders the card grid — so what does a visitor without JS see? A blank page would be the failure mode. Googlebot runs JavaScript these days, but simpler crawlers and real no-JS users don't.

So blog/index.html carries a <noscript> block: every article as a plain link, newest first, with a comment in the source that says "Regenerate from blog-data.json when publishing (keep in sync with the JS grid above)". JS visitors see the card grid instead — the page deliberately never shows both. One list for humans with JS, one plain list for everyone else, no duplicate listing.

There's also a hardcoded three-card "Latest articles" row for featured posts — not from the JSON, chosen by hand. Some curation refuses to be automated.

The honest part: the maintenance tax

Here's what five files cost you. Every new post touches five places: the HTML file, the JSON entry, the RSS item (gated), the sitemap URL, and the noscript list. All hand-synced. There is no build to catch the drift — the publishing checklist is the build.

That sounds worse than it is. A checklist you run in two minutes, in exchange for: zero dependencies, zero build minutes, zero framework upgrades, and every failure mode debuggable with view-source. The engine has no moving parts, so nothing can break while you're asleep. The worst failure is a post missing from one list — visible, obvious, fixed in one commit.

The stealable pattern

If you're building a docs site or a blog on a static host: one JSON index, client-side render, a <noscript> mirror list for crawlers and no-JS users, an RSS file with a parse gate (not vibes), and sitemap dates from git history. No build step required. The checklist is the build.

Try it

I build NitroIDE — a browser-based IDE where your code never leaves your machine: nitroide.com

⭐ If you enjoyed this, star NitroIDE on GitHub: github.com/nitroideofficial/nitroide

Frequently asked questions

How do you run a blog with no CMS and no build step?

With five files: one JSON file (blog-data.json) as the index, one self-contained HTML page per post, a client-side-rendered index page, a hand-edited RSS file, and a sitemap. The publishing checklist replaces the build step — every post is verified against each file before it ships.

How do search engines read a blog index that renders with JavaScript?

They don't have to: blog/index.html carries a <noscript> block with every article as a plain link, regenerated from blog-data.json at publish time. JS visitors see the card grid instead — the page never shows both.

How do you keep a hand-edited RSS feed from breaking?

With a validation gate before every commit: the file must parse as XML, and the count of <item> opens must equal closes. The gate exists because the live feed broke three times — Sep 2026, Oct 8, and Oct 10 — a missing closing tag each time.

Where do the sitemap's lastmod dates come from?

From git history: each URL's <lastmod> is derived from git log -1 --format=%cs for that file. A sitemap that claims everything changed today teaches crawlers to stop trusting it.