Why Astro for content-heavy sites
After shipping two Astro sites in a row, here's why it keeps winning out over Next.js and 11ty for content-driven solo-dev projects. Static-first, islands when you need them, Markdown content collections that just work.
Written by Hong-Bin Yoon · Founder, zzinDev LLC
Published
I’ve shipped two Astro sites in the last six months — AnimeRecap and, now, this one. Before that I was on Next.js for most content-ish work. I’m not planning to go back for content sites. Here’s why.
This isn’t a framework comparison post. It’s a list of the things Astro gets right for the specific shape of project that is “a content site with a handful of interactive bits,” and the things I’d pick Next.js for instead when that shape doesn’t fit.
The shape of project Astro wins at
A content-heavy site is mostly static pages rendered from Markdown or a CMS, with small islands of interactivity (search, a dynamic widget, maybe an embedded form). Most of the page is prose. Most of the page doesn’t need JavaScript.
Next.js and every other React-first framework treat this project as a special case of “everything is a React app.” You opt out of hydration per-component to ship less JavaScript, you opt into static generation per-route to avoid running Node.js at request time, and you fight the defaults until the output looks like what you wanted to begin with.
Astro’s default is the opposite. Pages are static HTML. JavaScript only ships for the components you explicitly mark as interactive (client:load, client:idle, etc). Markdown rendering is first-class, not bolted on. Content collections have schemas that fail the build if your frontmatter is wrong.
For AnimeRecap, the HTML size of a typical season-recap page is about 60KB and the JavaScript shipped is just the Pagefind search widget. On the same amount of prose, a Next.js implementation would ship the React runtime and a hydration payload for every page. Not because Next does it wrong; because it’s designed for different work.
Content collections
The thing that seals it for me is content collections. You declare a schema in src/content/config.ts:
import { defineCollection, z } from 'astro:content';
const posts = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
publishedAt: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});
export const collections = { posts };
Then any Markdown file in src/content/posts/ has its frontmatter validated at build time against that Zod schema. A typo in publishedAt, a missing title, a tags array that isn’t actually an array — build fails with a helpful error pointing at the specific file and field.
In Next.js I’d have to write that validation myself, or pull in a CMS abstraction, or accept that frontmatter errors will ship to production. In 11ty you can layer it with plugins, but it’s a layer. In Astro it’s the default.
Fetching content is getCollection('posts'), which returns a typed array. The body renders via const { Content } = await post.render(); and <Content />. That’s the whole API. I’ve been writing it for months and I still don’t have to look it up.
Islands that actually are islands
Most of what you read about Astro mentions “islands architecture,” the pitch being that you ship interactive components only where needed. The pitch is true but understates how noise-free the default is.
A typical AnimeRecap page has zero client-side JavaScript unless it opts in. The scroll-linked scene animations are a custom component with an is:inline script tag attached; Pagefind’s search widget is a separate bundle loaded by a <script> tag only on pages that need it. Everything else is HTML. Prose pages load and render before React would have finished parsing.
The mental model is: by default, this is static HTML. You add interactivity as an explicit choice, not by accident. That’s the inverse of how every SPA framework works, and it’s the right default for content.
Markdown as a first-class citizen
Astro treats Markdown as a page type. A file at src/pages/about.md becomes the /about/ route. Same with MDX if you want components mixed in. The build pipeline handles syntax highlighting (via Shiki, out of the box, with configurable themes), footnotes, smart quotes, anchor links, and HTML in markdown without surprises.
Inline code renders with the right language highlighting. Fenced code blocks support line highlighting via a {1,3-5} annotation. Tables render. Task lists render. All the standard GFM features are there.
The contrast with Next.js is that you pick your Markdown engine (MDX, next-mdx-remote, contentlayer, rehype-something), stitch together plugins for highlighting, set up your content directory, and end up with an approximation of what Astro does by default. Again — not because Next is bad, but because Astro is built for this and Next isn’t.
Build output
A clean build of AnimeRecap is 293 HTML files, a few CSS files, a handful of JS bundles for the interactive widgets, and the Pagefind index. Firebase Hosting serves it all. No Node process runs in production. No cold starts. No container to keep warm.
When something breaks, it’s because a build failed or a file wasn’t uploaded. You diff the dist/ directory against what’s on the host. That’s the whole debugging surface. There’s no “did the edge function time out” or “is the SSR cache stale” branch of the tree.
For a solo-dev side project, the absence of a runtime is the absence of a whole category of 3am pages.
Where I’d still pick something else
Astro is not the universal answer.
Apps with heavy client state. If your product is primarily an interactive app — a Kanban board, a drawing tool, a dashboard that needs live data — React in Next.js (or SvelteKit, or SolidStart) gives you better ergonomics for the hard parts. Astro islands are great for “sprinkle interactivity on content,” not for “the whole thing is interactive.”
Authenticated, personalized content. Astro supports SSR and has adapters for deploying to platforms that run it. But if your site is fundamentally per-user — think Gmail, Figma, or even a social feed — a framework built around that model is going to fit better.
Real-time. Same story. If you need WebSockets, collaborative editing, live cursors, or anything that requires a long-running server relationship, pick something built for that.
Teams already expert in React. If your team knows Next inside-out, the cost of switching to Astro for a content site is probably higher than the benefit. Tooling familiarity beats framework optimality for most project timeframes.
What I actually use Astro for
- Content sites. AnimeRecap (content catalog), this site (company + blog), older project that was blog-shaped.
- Documentation sites. I’ve built internal docs on Astro + Starlight; the DX is excellent and the output is as fast as static docs get.
- Marketing pages. Single-page sites where I want MDX for flexibility but no runtime to manage.
I don’t use Astro for apps. Those get Flutter (Lessonary’s case) or Next if they need to feel native on the web.
The boring conclusion
Astro is a good default for “a site that is mostly content, sometimes interactive, needs to build fast and host cheap, and should be easy to reason about a year from now when I come back to it.”
That’s a lot of projects. If your project fits that description and you’ve been reaching for Next or Gatsby or Nuxt by habit, try Astro for your next one. It’s about a day to get fluent. The day pays itself back quickly.
If your project doesn’t fit that description, pick something else. No framework wins every project. The trick is knowing which project you actually have.
Spot an error or have a suggestion? Request an edit →