Astro is a web framework that ships zero JavaScript by default. It renders HTML on the server and only hydrates interactive components — use React, Vue, Svelte, or Solid in the same project.
Why Astro?
- Zero JS by default — ships pure HTML, CSS
- Islands architecture — hydrate only interactive parts
- Any UI framework — React + Vue + Svelte in one project
- Content collections — type-safe Markdown/MDX content
- Blazing fast — consistently tops web framework benchmarks
Quick Start
npm create astro@latest my-site
cd my-site
npm run dev # http://localhost:4321
Pages (File-Based Routing)
---
// src/pages/index.astro
const title = 'My Site';
const users = await fetch('https://api.example.com/users').then(r => r.json());
---
<html>
<head><title>{title}</title></head>
<body>
<h1>{title}</h1>
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
</body>
</html>
Islands (Partial Hydration)
---
// src/pages/dashboard.astro
import ReactChart from '../components/Chart.tsx'; // React
import VueSearch from '../components/Search.vue'; // Vue
import SvelteCounter from '../components/Counter.svelte'; // Svelte
---
<html>
<body>
<h1>Dashboard</h1>
<!-- Static by default (zero JS) -->
<p>This paragraph ships no JavaScript.</p>
<!-- Hydrate on load -->
<ReactChart client:load data={chartData} />
<!-- Hydrate when visible (lazy) -->
<VueSearch client:visible />
<!-- Hydrate on idle -->
<SvelteCounter client:idle />
<!-- Never hydrate (server-render only) -->
<ReactChart data={chartData} />
</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 };
---
title: "My First Post"
date: 2026-03-29
tags: ["astro", "web"]
---
# My First Post
This is rendered as HTML at build time.
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => !data.draft);
---
{posts.map(post => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
<time>{post.data.date.toLocaleDateString()}</time>
</article>
))}
API Endpoints
// src/pages/api/users.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
const users = await db.select().from(usersTable);
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.insert(usersTable).values(body).returning();
return new Response(JSON.stringify(user), { status: 201 });
};
Performance
| Astro | Next.js | Remix | |
|---|---|---|---|
| JS shipped (blog) | 0 KB | ~90 KB | ~70 KB |
| Build time (1000 pages) | 3s | 15s | 12s |
| Lighthouse score | 100 | 92-98 | 95-99 |
Key Features
| Feature | Details |
|---|---|
| Rendering | SSG, SSR, hybrid |
| JS shipped | Zero by default, opt-in islands |
| Frameworks | React, Vue, Svelte, Solid, Preact, Lit |
| Content | Collections with Zod validation |
| Deploy | Vercel, Netlify, Cloudflare, Node, Deno |
| i18n | Built-in routing |
Resources
Building content sites? Check my Apify actors or email spinov001@gmail.com.
Top comments (0)