Astro 7 and the Return of HTML
Notes on building a fast, content-first website with Astro 7 — and why the framework's defaults feel like a quiet rebellion.
I have been rebuilding this site on Astro 7, and I keep being surprised by how much of the framework is just HTML. That sounds like a criticism, but it is the highest compliment I can pay a web framework in 2025.
The dominant pattern of the last decade has been to ship a small (or not-so-small) JavaScript runtime to the browser, hydrate a component tree, and let the framework take over rendering. The result is a web that is interactive but also heavy — a web where opening a blog post can download 300 KB of JavaScript to display 2 KB of text.
Astro inverts this. Pages are rendered to static HTML at build time. JavaScript is opt-in, not opt-out. The default mental model is “this is a document” rather than “this is an application,” and the framework gets out of the way when you want it to.
Content collections
The thing I am most excited about in the current Astro is content collections. Instead of dropping Markdown files in a folder and hoping the frontmatter is right, you define a schema:
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date(),
draft: z.boolean().default(false),
tags: z.array(z.string()).default([]),
}),
});This is the kind of unglamorous feature that quietly changes how you work. Typos in frontmatter — pubdate instead of pubDate, a string where you wanted a date — fail at build time, not at runtime. The collection becomes a typed object you can import anywhere. No more gray-matter calls scattered through utility files.
Islands, when you need them
Astro’s “islands” pattern is the second piece. Most of the page is static HTML. Interactive components are opt-in:
---
import Counter from '../components/Counter.jsx';
---
<Counter client:visible />The client:visible directive tells Astro: do not ship this component’s JavaScript until the user has scrolled to it. There are other directives — client:idle, client:load, client:only — and together they make it possible to build a page where the heavy stuff (a code playground, a chart, an interactive demo) loads lazily while the rest of the page renders instantly.
For a writing site like this, the value is mostly negative: I do not need any of that, so I do not ship any of that. The homepage loads in well under a second on a cold connection. The reading experience is the content.
What I miss
To be honest, not much. The trade-offs Astro makes are trade-offs I want to make anyway. The only mild annoyance is that some third-party React components assume they are running in a Next.js-shaped environment, and need a little coaxing to work as Astro islands. But this is a one-time cost per component, not a daily annoyance.
The quiet rebellion
What I like most about Astro is the worldview it implies. It is a framework that assumes the web is, fundamentally, a place to publish documents — and that interactivity is a seasoning, not the main course. That feels like a small act of resistance against a trend that has spent ten years turning every website into a single-page application.
The result, at least for me, is a site that is faster to build, faster to load, and easier to maintain. The fact that it is also faster for readers is the part that matters most.
Subscribe
Get new essays in your inbox.
Slow, considered writing — about once a month. No spam, unsubscribe anytime.