For most of my career, reaching for React (or Next.js) on any frontend project was automatic. The ecosystem is good, the patterns are familiar, and the friction is low enough that it stops feeling like a decision. You just start.
The problem is that React is optimised for interactive applications. A portfolio site, a marketing page, or a docs site is not an interactive application. It is mostly text, occasionally with a filter or a search widget. Shipping 200kb of JavaScript runtime to render a page that is 95% static feels like it should bother me more than it does in practice — but once I tried Astro, it did.
What Astro does differently
The core idea in Astro is zero JavaScript by default. A .astro component is a server-side template. It renders to HTML at build time and ships nothing to the browser unless you explicitly ask it to. If the page has no interactive components, the page has no JavaScript.
The portfolio site you are reading right now is built with Astro. The entire site ships under 50kb of JavaScript — most of it the View Transitions runtime for smooth page navigation. The homepage typing animation is a small inline script. Everything else is HTML and CSS.
Content collections
The feature I reach for most is content collections. You define a schema:
const writing = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/writing' }),
schema: z.object({
title: z.string(),
slug: z.string(),
date: z.string(),
summary: z.string(),
tags: z.array(z.string()),
}),
});
Drop markdown files in the directory, get type-safe access to frontmatter across your site. No CMS, no database, no API. The content layer is the filesystem.
For a portfolio or a technical blog, this is exactly the right level of complexity. The content is in version control, the schema is enforced at build time, and the whole thing deploys as a static folder.
View Transitions
Astro’s ClientRouter (the View Transitions API adapter) gives SPA-style navigation with no framework overhead. Pages navigate with a smooth crossfade. Scroll position is restored. Back/forward works. It feels like a React app to the user and a static site to the network.
The implementation is one import in the layout:
import { ClientRouter } from 'astro:transitions';
That is it. No router configuration, no code splitting setup, no bundle analysis.
Where it still has rough edges
Content collections in Astro 6 are mid-refactor. The z import from astro:content is deprecated in favour of importing Zod directly, but the migration path is not yet obvious from the documentation. Build warnings appear, the build still succeeds, and the docs do not mention astro:schema yet. A minor friction that will resolve itself, but worth knowing if you start a new project today.
The dev server is fast but not instant — adding a new content collection requires a restart because the type generation does not pick it up automatically. Again, a minor workflow irritant, not a blocker.
When I would use something else
Astro is a build-time tool. If your content is dynamic — user-generated, frequently updated, personalised per visitor — you are fighting the grain. Use Next.js, Remix, or a proper edge-deployed framework instead.
For anything that is mostly content with occasional interactivity, Astro is the right call. It delivers fast pages with minimal runtime overhead, keeps the architecture simple, and gets out of the way.