Frameworks are overkill. HTML is enough.

If you just want to publish Markdown, use Hugo and let an agent set it up. It's one binary, it builds in a tenth of a second, and it ships plain HTML. This post is for the other case: when you want every page to be your own HTML, built by a script small enough to read and change.

A blog post is a document. The browser already knows how to render one. A static site is that fact, plus a folder of files.

A static site is the simple default

One author, one folder. Nothing has to run on a server to show the article, and nothing has to boot in the browser before the words are on screen.

  • The page opens immediately. The HTML is the page.
  • It works offline, and it works with JavaScript disabled.
  • Search engines read the HTML as it is. Nothing has to render first.
  • Hosting is a file on a CDN. The cache holds until the content changes.
  • The file you wrote is the file that ships.
  • A small build can still produce images, RSS, search, and SEO.

Raw against a framework

The job is the same: publish a document. Raw HTML skips what a framework adds on the way there.

  • The article is HTML. Nothing reattaches it to a client runtime.
  • The body ships with no JavaScript bundle.
  • A paragraph goes live without an install.
  • A typo fix does not wait on a framework upgrade.
  • A file from years ago still opens.
  • A demo is one extra file on that page, not a runtime on every page.

A framework earns its place on shared application UI, auth, mutations, and live state. A post is a document.

What raw gives up, and what covers it

A pile of HTML files has real gaps. Each one has a small fix.

Repeated chrome

Headers, titles, and canonical URLs get tedious by hand. A thin build script owns that, plus RSS, the sitemap, and search. It runs once, over files, and stays off the page.

One-off layouts

A diagram, a demo, or a different grid does not belong in a shared component library. It is one HTML file with its own CSS and, when the page needs it, its own script.

Producing the page

Custom layout used to take a developer for a day. An LLM drafts the HTML and CSS from a description. You edit the file and ship it.

Images and metadata

Pictures, Open Graph tags, and the feed still need a pass at build time. That pass is a script on the build machine.

The numbers

I built the same article four ways: hand-written HTML, Hugo, Astro, and Next.js. Then I measured what each costs to produce and what the browser downloads.

Same text, same 1.2 KB stylesheet, default settings. Next.js is an App Router static export with no UI library. Builds are the median of three clean runs on an M1 Pro. Page numbers are from Lighthouse on its mobile preset, with every page served as static files over gzip.

Toolchain

What it takes to produce the page. Hugo is a single binary with no packages.
Measure Raw Hugo Astro Next.js
Direct dependencies 0 0 1 3
Packages installed 0 0 192 24
On disk — 80 MB 152 MB 362 MB
Clean build — 0.1 s 0.9 s 2.8 s

Next.js bundles most of its own dependencies, so it installs few packages and still takes the most disk. Its compiler alone is 86 MB. For scale, this site's own build script has 6 dependencies and takes 41 MB.

The page

What the browser downloads and runs for the article. Sizes are over the wire unless marked.
Measure Raw Hugo Astro Next.js
Requests 2 2 1 7
HTML 1.4 KB 1.5 KB 2.0 KB 3.3 KB
HTML uncompressed 2.6 KB 2.8 KB 3.7 KB 11.6 KB
CSS 0.7 KB 0.7 KB inlined 0.7 KB
JS files 0 0 0 5
JS 0 0 0 133 KB
JS uncompressed 0 0 0 446 KB
Total transfer 2.2 KB 2.2 KB 2.0 KB 137 KB
First contentful paint 0.75 s 0.75 s 0.63 s 0.76 s
JS execution 0 ms 0 ms 0 ms 105 ms
Readable with JS off Yes Yes Yes Yes

Hugo and Astro ship the same page as raw HTML. Astro even saves a request by inlining the small stylesheet. What they add is a toolchain and a build step, and Hugo's build takes a tenth of a second.

Next.js paints the text just as fast, because it ships HTML too. Then it downloads 133 KB of JavaScript and runs it for 105 ms on Lighthouse's throttled phone, to hydrate a page with nothing to hydrate. The HTML is four times bigger as well: the article is in it twice, once as markup and once as the React Server Components payload.

What the tables leave out

Two things don't fit in a table: how much you have to learn, and what happens when a page needs more than text. Every option here can show an animated SVG. They differ in how much of the tool stands between you and the file.

  • Raw. The SVG and its script go in the page. You need HTML, CSS, and JavaScript, which you need anyway.
  • Hugo. One binary and nothing to install, but layouts are Go templates. Markdown drops raw HTML unless you turn on unsafe, so an inline SVG needs that setting or a shortcode. A page's own script needs a layout that looks for it.
  • Astro. Inline SVG works in Markdown. A script goes in an Astro component, and Vite bundles it. You learn Astro's component format and its build.
  • Next.js. The SVG is JSX. A drawing driven by a script is a client component, so its code joins the bundle that hydrates. You learn React, the App Router, and which code runs where.
  • This site. A post folder can hold interactive.js and styles.css, and the build adds them to that page only. The rest is Markdown.

Why I still use my own build

The reader gets the same page from Hugo, Astro, and this site. The difference is all on my side:

  • I own the build. It's one script of about 1,200 lines. A new feature is a function I write, not a plugin I go looking for.
  • Custom pages are plain files. No shortcodes, no components, no template language.
  • An agent can read all of it. The whole build fits in its context, so it never has to guess a framework's API for the version I'm on.
  • Nothing to upgrade. No releases to follow and no migration guides.

The price is real. Hugo builds faster and has image processing, taxonomies, and pagination built in. Astro has components and a dev server with hot reload. And when my build breaks, I'm the one who fixes it.

If you want a generator, pick one that ships HTML. The page is a document in every column. Only one of them makes the reader pay for an application.