Astro - Complete Guide to Island Architecture and Content-First Web Development
What is Astro and Why is it Different?
Astro is a modern web framework that revolutionizes how we build websites through its Island Architecture. The core philosophy is "ship less JavaScript"βby default, Astro pages send zero JavaScript to the browser, resulting in blazing-fast load times and excellent Core Web Vitals scores.
What makes Astro unique is its ability to use your favorite UI frameworks (React, Vue, Svelte, Solid) as isolated islands of interactivity in a sea of static HTML. You can have a React slider and a Vue form on the same page, loading JavaScript only where it's truly needed.
Why Choose Astro?
Astro vs Other Frameworks
| Feature | Astro | Next.js | Gatsby | SvelteKit |
|---|---|---|---|---|
| Default JS | 0 KB | ~80 KB+ | ~50 KB+ | ~15 KB |
| Multi-framework | Yes | React only | React only | Svelte only |
| Content focus | Built-in | Plugin | Built-in | Manual |
| Island Architecture | Native | No | No | No |
| Learning curve | Low | Medium | High | Medium |
| Build time | Fast | Medium | Slow | Fast |
Key Advantages
- Zero JavaScript by default - Pages are static HTML, JS loaded on demand
- Island Architecture - Isolated interactive components
- Framework agnostic - Use React, Vue, Svelte, Solid together
- Content Collections - Type-safe content management
- View Transitions - Smooth page transitions native
- Excellent SEO - Static HTML = perfect indexing
- Server Islands - Dynamic islands rendered on server
When to Choose Astro
- Blogs and documentation - Perfect for content-first sites
- Landing pages - Maximum performance and SEO
- Portfolios - Fast, responsive sites
- E-commerce (product pages) - Static pages + dynamic carts
- Marketing sites - Excellent Core Web Vitals
- Hybrid apps - Mostly static with interactive elements
Installation and Setup
Creating a New Project
# Create new Astro project
npm create astro@latest
# Or with a starter template
npm create astro@latest -- --template blog
npm create astro@latest -- --template docsProject Structure
my-astro-site/
βββ src/
β βββ components/ # UI components
β βββ layouts/ # Page layouts
β βββ pages/ # File-based routing
β βββ content/ # Content collections
βββ public/ # Static assets
βββ astro.config.mjs # Astro configuration
βββ package.jsonAstro Components
Basic Component
---
// Component Script (runs at build time)
const greeting = 'Hello, Astro!'
const items = ['React', 'Vue', 'Svelte']
---
<!-- Component Template -->
<div class="card">
<h1>{greeting}</h1>
<ul>
{items.map((item) => <li>{item}</li>)}
</ul>
</div>
<style>
/* Scoped styles by default */
.card {
padding: 1rem;
background: white;
border-radius: 8px;
}
</style>Props and Slots
---
interface Props {
title: string
description?: string
}
const { title, description = 'Default description' } = Astro.props
---
<article>
<h2>{title}</h2>
<p>{description}</p>
<slot /> <!-- Children go here -->
<slot name="footer" /> <!-- Named slot -->
</article>Using Components
---
import Card from '../components/Card.astro'
import Button from '../components/Button.astro'
---
<Card title="Welcome" description="Hello world">
<p>This goes in the default slot</p>
<Button slot="footer">Click me</Button>
</Card>Pages and Routing
File-Based Routing
src/pages/
βββ index.astro β /
βββ about.astro β /about
βββ blog/
β βββ index.astro β /blog
β βββ [slug].astro β /blog/:slug (dynamic)
βββ [...slug].astro β catch-all routeDynamic Routes
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}))
}
const { post } = Astro.props
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>Island Architecture
What Are Islands?
Islands are interactive UI components on an otherwise static HTML page. They load JavaScript only when needed, keeping your site fast.
Framework Integrations
# Add React
npx astro add react
# Add Vue
npx astro add vue
# Add Svelte
npx astro add svelte
# Add multiple at once
npx astro add react vue svelteUsing Framework Components
---
import ReactCounter from '../components/Counter.jsx'
import VueForm from '../components/Form.vue'
import SvelteSlider from '../components/Slider.svelte'
---
<h1>Static HTML</h1>
<!-- React island - interactive -->
<ReactCounter client:load />
<!-- Vue island - interactive on visible -->
<VueForm client:visible />
<!-- Svelte island - interactive on idle -->
<SvelteSlider client:idle />Client Directives
<!-- Load immediately -->
<Counter client:load />
<!-- Load when browser is idle -->
<Counter client:idle />
<!-- Load when component is visible -->
<Counter client:visible />
<!-- Load on specific media query -->
<Counter client:media="(max-width: 768px)" />
<!-- Only load on client, skip SSR -->
<Counter client:only="react" />Content Collections
Defining Collections
// src/content/config.ts
import { defineCollection, z } from 'astro:content'
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
author: z.string(),
image: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
})
export const collections = {
blog: blogCollection,
}Content Files
---
// src/content/blog/my-first-post.md
title: My First Post
description: Welcome to my blog
pubDate: 2024-01-15
author: John Doe
tags: [astro, web]
---
# Hello World
This is my first blog post written in Markdown!Querying Collections
---
import { getCollection, getEntry } from 'astro:content'
// Get all posts
const allPosts = await getCollection('blog')
// Filter posts
const publishedPosts = await getCollection('blog', ({ data }) => {
return !data.draft
})
// Get single entry
const post = await getEntry('blog', 'my-first-post')
---
<ul>
{publishedPosts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
</li>
))}
</ul>Layouts
Creating a Layout
---
// src/layouts/BaseLayout.astro
interface Props {
title: string
description?: string
}
const { title, description = 'My Astro Site' } = Astro.props
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2024 My Site</p>
</footer>
</body>
</html>Using Layouts
---
import BaseLayout from '../layouts/BaseLayout.astro'
---
<BaseLayout title="Home Page">
<h1>Welcome!</h1>
<p>This content goes in the slot.</p>
</BaseLayout>View Transitions
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions'
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html><!-- Animate specific elements -->
<h1 transition:name="title">Page Title</h1>
<img
src="/hero.jpg"
transition:name="hero"
transition:animate="fade"
/>
<!-- Animation options: fade, slide, none -->
<div transition:animate="slide">Content</div>API Routes (Server Endpoints)
// src/pages/api/posts.ts
import type { APIRoute } from 'astro'
export const GET: APIRoute = async ({ params, request }) => {
const posts = await getPosts()
return new Response(JSON.stringify(posts), {
headers: { 'Content-Type': 'application/json' },
})
}
export const POST: APIRoute = async ({ request }) => {
const data = await request.json()
const post = await createPost(data)
return new Response(JSON.stringify(post), { status: 201 })
}Hybrid Rendering
SSR Configuration
// astro.config.mjs
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
export default defineConfig({
output: 'hybrid', // or 'server' for full SSR
adapter: node({
mode: 'standalone',
}),
})Per-Page Rendering
---
// Force static generation
export const prerender = true
------
// Force server rendering (in hybrid mode)
export const prerender = false
const user = await getUser(Astro.cookies.get('session'))
---Styling Options
Scoped Styles
<style>
/* Scoped to this component only */
h1 {
color: blue;
}
</style>Global Styles
<style is:global>
/* Applies globally */
body {
font-family: system-ui;
}
</style>Tailwind CSS
npx astro add tailwind<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow">
<h1 class="text-2xl font-bold text-gray-900">Hello</h1>
</div>Image Optimization
---
import { Image } from 'astro:assets'
import heroImage from '../images/hero.png'
---
<!-- Optimized image -->
<Image
src={heroImage}
alt="Hero image"
width={800}
height={400}
/>
<!-- Remote image -->
<Image
src="https://example.com/image.jpg"
alt="Remote image"
width={800}
height={400}
inferSize
/>Deployment
Static (Default)
# Build static site
npm run build
# Output in dist/ folder
# Deploy to any static host: Netlify, Vercel, GitHub PagesVercel
npx astro add vercelNetlify
npx astro add netlifyCloudflare Pages
npx astro add cloudflareBest Practices
Performance
<!-- Use client directives wisely -->
<!-- client:load - Only for above-the-fold interactive content -->
<!-- client:visible - Preferred for below-the-fold content -->
<!-- client:idle - For non-critical interactivity -->
<!-- Prefer Astro components for static content -->
<Card title="Static" />
<!-- Use framework components only when needed -->
<ReactCounter client:visible />Content Organization
src/content/
βββ blog/ # Blog posts
β βββ post-1.md
β βββ post-2.md
βββ docs/ # Documentation
β βββ intro.md
β βββ guides/
βββ config.ts # Collection schemasSummary
Astro is the ideal framework for content-focused websites:
- Zero JavaScript by default - Maximum performance out of the box
- Island Architecture - JavaScript only where needed
- Multi-framework - Use React, Vue, Svelte, Solid together
- Content Collections - Type-safe content management
- View Transitions - Native smooth page transitions
- Excellent SEO - Static HTML = perfect indexing
- Hybrid rendering - Static + SSR when needed
Astro combines the performance benefits of static site generation with the flexibility of interactive islands, making it perfect for blogs, documentation, marketing sites, and any content-first web project.