Introduction
Astro has fundamentally changed how we think about building websites. Unlike traditional SPA frameworks that ship heavy JavaScript to the browser, Astro takes a “static-first” approach, shipping zero JavaScript by default. This philosophy has resonated with developers, making Astro one of the fastest-growing frameworks in the ecosystem.
Astro 5.0, released in 2026, represents the most significant update to the framework. It introduces the Content Layer API for structured content management, Server Islands for dynamic content in static sites, and numerous performance improvements.
This comprehensive guide covers everything you need to know about Astro 5.0. Whether you’re building a blog, documentation site, or marketing page, you’ll learn how to leverage Astro’s unique approach for maximum performance.
Why Choose Astro in 2026
The Astro Philosophy
Astro’s core philosophy is simple: ship less JavaScript. Here’s how it achieves this:
| Approach | JavaScript Sent | Performance |
|---|---|---|
| Traditional SPA | Full bundle | Slower, more CPU usage |
| Astro | Zero by default | Instant, efficient |
| Islands Architecture | Only interactive parts | Optimal balance |
Key Benefits
- Zero JS by default: HTML-only pages are instant
- Islands Architecture: Hydrate only what’s needed
- Content Collections: Type-safe content management
- Framework Agnostic: Use React, Vue, Svelte, etc.
- Server Islands: Dynamic content in static sites
Getting Started with Astro 5.0
Installation
Create a new Astro project:
# Using the CLI
npm create astro@latest my-astro-site
# Select options:
# - Include sample files: Yes
# - Install dependencies: Yes
# - TypeScript: Strict
cd my-astro-site
npm run dev
Project Structure
my-astro-site/
โโโ src/
โ โโโ components/
โ โ โโโ Header.astro
โ โ โโโ Counter.tsx # React component
โ โโโ content/
โ โ โโโ config.ts
โ โ โโโ blog/
โ โ โโโ hello-world.md
โ โโโ layouts/
โ โ โโโ BaseLayout.astro
โ โโโ pages/
โ โ โโโ index.astro
โ โ โโโ blog/[...slug].astro
โ โโโ styles/
โ โโโ global.css
โโโ public/
โโโ astro.config.mjs
โโโ package.json
Content Collections
Defining Collections
Astro 5.0’s Content Layer API provides type-safe content management:
// src/content/config.ts
import { defineCollection, z } from 'astro:content'
const blog = defineCollection({
type: 'content', // v2.5+ uses 'content' or 'data'
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
updatedDate: z.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false)
})
})
const docs = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
order: z.number(),
category: z.enum(['getting-started', 'guides', 'api'])
})
})
export const collections = {
blog,
docs
}
Using Collections
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content'
const posts = await getCollection('blog', ({ data }) => {
return !data.draft
})
const sortedPosts = posts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
)
---
<h1>Blog</h1>
<ul>
{sortedPosts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>
<h2>{post.data.title}</h2>
<p>{post.data.description}</p>
</a>
</li>
))}
</ul>
Server Islands
Server Islands are revolutionary. They let you render dynamic content within static pages:
Basic Server Island
---
// src/components/Weather.astro
// This runs on the server at request time
const weather = await fetch(
'https://api.weather.com/current?city=London'
).then(r => r.json())
---
<div class="weather">
<p>Current temperature: {weather.temp}ยฐC</p>
<p>Condition: {weather.condition}</p>
</div>
Using Server Islands in Pages
---
// src/pages/index.astro
import Header from '../components/Header.astro'
import Footer from '../components/Footer.astro'
import Weather from '../components/Weather.astro'
import InteractiveCounter from '../components/Counter.tsx'
---
<html>
<head>
<title>My Site</title>
</head>
<body>
<Header /> <!-- Static -->
<main>
<h1>Welcome</h1>
<!-- Server Island - renders at request time -->
<Weather server:defer />
<!-- Client Component - hydrates on client -->
<InteractiveCounter client:visible />
</main>
<Footer /> <!-- Static -->
</body>
</html>
Server Island Options
| Directive | Behavior |
|---|---|
server:defer |
Stream in after page load |
server:load |
Render immediately on server |
server:only |
Server-only, no SSR HTML |
Astro Components
Component Syntax
---
// src/components/Card.astro
interface Props {
title: string
description?: string
href: string
}
const { title, description = 'Learn more', href } = Astro.props
---
<article class="card">
<h2>{title}</h2>
{description && <p>{description}</p>}
<a href={href}>{description}</a>
</article>
<style>
.card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
}
</style>
Passing Props
---
import Card from '../components/Card.astro'
---
<Card
title="Getting Started"
href="/docs/getting-started"
description="Learn how to use Astro"
/>
Dynamic Routing
File-Based Routing
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content'
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map(post => ({
params: { slug: post.slug },
props: { post }
}))
}
const { post } = Astro.props
const { Content } = await post.render()
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
Query Parameters
---
// src/pages/search.astro
const q = Astro.url.searchParams.get('q')
const results = q ? await search(q) : []
---
<form action="/search">
<input name="q" value={q} />
<button type="submit">Search</button>
</form>
{results.length > 0 && (
<ul>
{results.map(result => (
<li><a href={result.url}>{result.title}</a></li>
))}
</ul>
)}
Integrations
Adding Frameworks
# Add React
npx astro add react
# Add Vue
npx astro add vue
# Add Svelte
npx astro add svelte
# Add Tailwind
npx astro add tailwind
Using Framework Components
---
// src/pages/index.astro
import ReactCounter from '../components/ReactCounter.tsx'
import VueCounter from '../components/VueCounter.vue'
import SvelteCounter from '../components/SvelteCounter.svelte'
---
<!-- Each hydrates independently -->
<ReactCounter client:visible />
<VueCounter client:visible />
<SvelteCounter client:visible />
Hybrid Rendering
Switching to Server Rendering
// astro.config.mjs
import { defineConfig } from 'astro/config'
export default defineConfig({
output: 'hybrid', // or 'server' for full SSR
adapter: vercel()
})
Hybrid Page by Page
---
// src/pages/about.astro
// This page is static (default)
export const prerender = true
---
<h1>About - Static</h1>
---
// src/pages/dashboard.astro
// This page is server-rendered
// No prerender = server-rendered in hybrid mode
---
<h1>Dashboard - Dynamic</h1>
Data Fetching
Fetch in Frontmatter
---
// src/pages/users.astro
const response = await fetch('https://api.example.com/users')
const users = await response.json()
---
<ul>
{users.map(user => (
<li>{user.name}</li>
))}
</ul>
Using Databases
---
// src/pages/posts.astro
import { db, Post } from '../lib/db'
const posts = await db.select().from(Post).limit(10)
---
<ul>
{posts.map(post => (
<li>{post.title}</li>
))}
</ul>
Images
Optimized Images
---
// src/pages/index.astro
import { Image } from 'astro:assets'
import myImage from '../assets/hero.png'
---
<Image
src={myImage}
alt="Hero image"
width={800}
height={600}
format="webp"
/>
External Images
---
import { Image } from 'astro:assets'
---
<Image
src="https://example.com/remote-image.jpg"
alt="Remote image"
width={800}
height={600}
/>
Styling
Scoped Styles
<style>
/* Only applies to this component */
h1 {
color: red;
}
</style>
Global Styles
<style is:global>
/* Applies everywhere */
html {
font-family: system-ui;
}
</style>
Tailwind Integration
npx astro add tailwind
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
Performance Optimization
Lazy Loading Components
---
// Only load when visible
import HeavyComponent from '../components/HeavyComponent'
---
<HeavyComponent client:visible />
Prefetching
---
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true,
defaultStrategy: 'viewport'
}
})
Image Optimization
---
import { Image } from 'astro:assets'
import heroImage from '../assets/hero.jpg'
---
<!-- Automatic WebP conversion, lazy loading, srcset -->
<Image src={heroImage} alt="Hero" />
Deployment
Vercel
npx astro add vercel
// astro.config.mjs
import vercel from '@astrojs/vercel'
export default defineConfig({
output: 'hybrid',
adapter: vercel()
})
Netlify
npx astro add netlify
Node.js
npx astro add node
External Resources
Official Documentation
Learning Resources
Tools
- Astro Studio - Cloud database
- Deploy Button - One-click deploy
Conclusion
Astro 5.0 represents the future of web development. Its static-first approach with Islands Architecture provides the best of both worlds: the performance of static HTML with the interactivity of modern frameworks.
Key takeaways:
- Content Collections: Type-safe content management
- Server Islands: Dynamic content in static sites
- Islands Architecture: Ship less JavaScript
- Framework Agnostic: Use your favorite framework
- Hybrid Rendering: Mix static and dynamic as needed
Whether you’re building a blog, documentation, marketing site, or web app, Astro 5.0 provides the tools and performance you need to succeed.
Related Articles
- Svelte 5 Complete Guide
- Vue 4 Complete Guide
- HTMX Complete Guide: Simpler React Alternative
- Qwik Complete Guide: Zero-Hydration Framework
Comments