Astro is the web framework for content-driven websites. It ships zero JavaScript by default, supports React/Vue/Svelte components, and has the best performance scores of any framework.
Why Astro?
- Zero JS by default — HTML-first, JS only where you need it
- Islands architecture — hydrate only interactive components
- Any UI framework — React, Vue, Svelte, Solid — mix and match
- Content Collections — type-safe Markdown/MDX content layer
Quick Start
npm create astro@latest mysite
cd mysite
npm run dev
Pages
---
// src/pages/index.astro
const title = 'My Site';
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<html>
<head><title>{title}</title></head>
<body>
<h1>{title}</h1>
<ul>
{posts.map(post => (
<li><a href={`/posts/${post.slug}`}>{post.title}</a></li>
))}
</ul>
</body>
</html>
Content Collections
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
---
// 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();
---
<h1>{post.data.title}</h1>
<time>{post.data.date.toLocaleDateString()}</time>
<Content />
Islands (Partial Hydration)
---
import ReactCounter from '../components/Counter.jsx';
import VueSearch from '../components/Search.vue';
---
<!-- Static HTML - no JS -->
<h1>My Page</h1>
<p>This paragraph ships zero JavaScript.</p>
<!-- Interactive island - only this hydrates -->
<ReactCounter client:load />
<!-- Lazy island - hydrates when visible -->
<VueSearch client:visible />
<!-- Media query island -->
<MobileMenu client:media="(max-width: 768px)" />
API Routes
// src/pages/api/users.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
const users = await db.getUsers();
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' },
});
};
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const user = await db.createUser(body);
return new Response(JSON.stringify(user), { status: 201 });
};
View Transitions
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main transition:animate="slide">
<slot />
</main>
</body>
</html>
Building a content site that needs fresh data? Check out my Apify actors for web scraping to power your Astro content, or email spinov001@gmail.com for custom solutions.
Astro, Next.js, or 11ty — which do you use for content sites? Share below!
Top comments (0)