Astro Deep Dive: Islands Architecture & Content Collections
Astro ships zero JS by default, powered by Islands Architecture (selective hydration) and type-safe Content Collections, delivering peak performance with modern DX.
Islands Architecture and Hydration Strategies
Non-interactive content renders as pure static HTML. Only components with client:load/visible/idle/only fetch JS and hydrate. React/Svelte/Vue components coexist on same page independently.
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string().max(80),
description: z.string().max(200),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
cover: z.object({ src: z.string(), alt: z.string() }).optional(),
draft: z.boolean().default(false),
category: z.enum(['tutorial','technology','essays','translation']),
}),
});
const projects = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
name: z.string(), summary: z.string(),
hero: image(), stack: z.array(z.string()),
repo: z.string().url().optional(), live: z.string().url().optional(),
featured: z.boolean().default(false),
}),
});
export const collections = { blog, projects };
// src/pages/blog/[...slug].astro
/* ---
import { getCollection, type CollectionEntry } from 'astro:content';
import { Image } from 'astro:assets';
import LikeCounter from '../../components/LikeCounter.svelte';
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => import.meta.env.DEV || !data.draft);
return posts.map(p => ({ params: { slug: p.slug }, props: { post: p } }));
}
const { post } = Astro.props as { post: CollectionEntry<'blog'> };
const { Content } = await post.render();
--- ---
<article>
<header>
<h1>{post.data.title}</h1>
{post.data.cover && <Image src={post.data.cover.src} alt={post.data.cover.alt} widths={[400,800,1200]} />}
</header>
<Content />
<LikeCounter client:idle postSlug={post.slug} />
</article>
*/
Type-Safe Content Collections
Define Zod schemas per collection in content/config.ts. When calling getCollection, Astro validates frontmatter and throws at build time on invalid data. Built-in image optimization works through schemas.
| Directive | Trigger Timing | Overhead | Best For |
|---|---|---|---|
| Default | Never hydrates | Zero JS | Static cards, body |
| client:load | Immediate on load | Highest | Nav, core UI |
| client:idle | After first idle | Medium-low | Likes, secondary |
| client:visible | Enters viewport | Very Low | Footer, late items |
Best Practices
Start without client:* directives - add only after proving interactivity needed. Enforce strict collection schemas to avoid data drift. Pre-render every dynamic route via getStaticPaths + slug pattern.