Why I Still Choose Static-First Websites in 2026
I explain why I keep choosing static-first for blogs, docs, and portfolios, what it costs, and when I pay for a server instead.

I build interactive products for a living: dashboards, mobile apps, backend systems, and AI tools — the kind of work I do, where a server does real work on every request. You would expect me to build this website the same way.
I did not. This blog is generated to plain HTML at build time and deployed as static files. The article you are reading has no page framework running in your browser, and I like it that way.
Not every website needs a backend. Not every page needs hydration. Not every piece of content needs JavaScript. The pages here are known before you visit them, so they are generated before you visit them.
Quick answer: If a page’s content is known before the visitor arrives, generate it before they arrive. That covers blogs, portfolios, docs, changelogs, and landing pages, and it makes pages fast, cheap to host, and easy to crawl. Pages that depend on who is asking, or on data that changes per request, still need a server — static-first does not try to replace those.
On this page
- Static does not mean basic
- Content collections make the build tell the truth
- The performance you keep without a client runtime
- SEO is discipline, not rendering
- Where static-first is the wrong answer
- The rule I keep coming back to
Static does not mean basic
Some people hear “static” and think it means basic or outdated. I do not see it that way.
A static-first site can still have:
- Good typography
- Client-side search
- An RSS feed
- Dark mode
- Code highlighting
- Tag filtering
- Small interactive parts where needed
This site has all of the above. Search opens a command menu that fetches its index only when you open it. The tag filter on the blog index is a small script. Theme choice lives in local storage. None of that needs a page framework.
Try this
- Open this article and view the page source (right-click, then “View Page Source”).
- Search the source for a sentence from the first paragraph. It is in the HTML.
- Disable JavaScript in your browser settings and reload the page.
Expected result: The article, layout, and styles render normally. The command menu search stops working, because it needs its script. That gap is the entire cost of going static-first here, and I am fine paying it.
The difference is what ships by default. The default is HTML and CSS. JavaScript is added only where it earns its place. Astro calls this pattern islands: the page stays static, and a component becomes interactive only when you mark it.
Content collections make the build tell the truth
For a blog, the part I care about most is that content has a schema and a single source of truth. Astro’s content collections give me exactly that (this site runs Astro 7). Every post is a file in one folder, validated against a schema at build time:
import { glob } from "astro/loaders";
import { z } from "astro/zod";
import { defineCollection } from "astro:content";
const blogs = defineCollection({
loader: glob({ base: "./src/content/blogs", pattern: "**/*.{md,mdx}" }),
schema: z.object({
title: z.string(),
slug: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
}),
});
That is the real collection from this site’s content.config.ts, trimmed only of a second collection for reading notes. The loader picks up every Markdown and MDX file under src/content/blogs. The schema is a Zod object: pubDate is coerced to a real Date, updatedDate and heroImage are optional, and tags defaults to an empty array.
The behaviour I care about: if I typo a date or forget the description, astro build fails and names the file and the field. The mistake surfaces in CI, not after deploy. Content errors become build errors, and build errors are the cheapest errors I know.
The performance you keep without a client runtime
When a page ships less JavaScript, performance work becomes more predictable. There is no client runtime competing with the content.
To be clear, static HTML is not automatically fast. You can still ship a 4 MB hero image on a static page. You still need to care about:
- Font loading
- Image dimensions
- CSS size
- Layout shifts
- Cache headers
- Mobile viewport behaviour
But you are not tuning a hydration boundary or a render tree just to show text. Heavy JavaScript on the main thread is one of the main reasons interactions feel slow, and it is exactly what Interaction to Next Paint catches. If the text is already in the HTML, there is almost nothing to wait for.
For a content site, that trade is almost always worth it.
SEO is discipline, not rendering
Static output helps SEO because crawlers get real HTML without waiting for scripts. But static is the floor, not the finish line. The work that is left is discipline:
- One clear title per page
- A useful description
- A canonical URL
- Open Graph tags
- An RSS feed
- A sitemap
- Structured data where it makes sense
None of it is hard, and none of it happens by itself. On this site the sitemap is generated at build time, each article gets BlogPosting structured data automatically, and the feed is one file. The rest — titles, descriptions, internal links — is still on me.
Check this before you ship
- View-source shows the page title and description in the head
- The sitemap URL (
/sitemap-index.xmlhere) loads and lists your pages - The RSS feed opens and shows your latest posts
- Every page has exactly one canonical URL
- A link preview in a chat or social debugger shows the right title and image
A site going live should not treat these as an afterthought.
Where static-first is the wrong answer
The strongest argument against static-first is simple: real products serve real requests. The moment a page depends on who is asking, or on data that changes faster than you build, static output stops being the answer.
That includes:
- Dashboards and inboxes, where the HTML differs per user
- Anything behind a login
- Prices, stock levels, or anything real-time
- Search across private data
There is also a cost people skip: every content change is a rebuild. At a few posts a month, that is nothing. At hundreds of updates a day, you will reach for build hooks or on-demand rendering, which is a deliberate step back toward dynamic — and that is fine.
| Your situation | Recommended approach | Reason |
|---|---|---|
| Content known before the visit | Static at build time | Fastest, cheapest, nothing to monitor |
| HTML depends on who is asking | Server or edge rendering | The response is different per user |
| Content changes many times a day | Build hooks or hybrid | One rebuild per change does not scale |
| Small interactive area in static content | An island | Ship JavaScript for that area only |
This site lives in rows one and four. The pages are static; search, theme, and tag filtering are islands.
The rule I keep coming back to
If the content is known before the user visits the page, generate it before the user visits the page.
That one rule has kept almost every content site I build simple. Use dynamic rendering when the data is user-specific, private, real-time, or genuinely unknowable at build time. For everything else — blogs, docs, portfolios, changelogs, landing pages — static-first is still the strongest default I know.
If you are unsure, pick one page you own and view its source with JavaScript disabled. Ask what actually needed a server. Most of the time the honest answer is: nothing.