What I Learned Building with Astro
Content Collections, static paths, and the island architecture — a practical rundown.
After building this blog entirely in Astro, here are the key concepts worth understanding before you start.
Content Collections
Content Collections are Astro’s answer to type-safe markdown. You define a schema once in src/content/config.ts using Zod, and Astro validates every markdown file against it at build time.
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});
No more silent frontmatter typos causing runtime errors. If a post has an invalid date or missing required field, the build fails immediately.
Static Paths
Dynamic routes like blog/[slug].astro require a getStaticPaths() export. Astro calls this at build time to enumerate every page to generate.
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
This same pattern powers the tag pages — every tag in your posts automatically gets a pre-rendered listing page.
The Island Architecture
By default, every Astro component renders to static HTML. To add client-side interactivity, you use a client:* directive:
client:load— hydrates immediately on page loadclient:idle— hydrates when the browser is idleclient:visible— hydrates when the element enters the viewport
For this blog, only the theme toggle uses a small inline <script> tag. Everything else is pure HTML.
Build Performance
Running astro build on this blog takes under two seconds. The output is a folder of static HTML, CSS, and minimal JS — easily deployed to any static host (Netlify, Vercel, Cloudflare Pages, GitHub Pages).