Astro — Web Framework Cho Content-First Site
Astro là web framework content-first với Islands Architecture, zero JS mặc định. Content Collections, View Transitions, Server Islands cho site nhanh.
Astro là web framework cho content-first site. Khác với Next.js hay Nuxt — Astro ship zero JavaScript mặc định, chỉ thêm JS khi cần.
Next.js: page → React → JS bundle → gửi JS cho client
Astro: page → HTML → gửi HTML cho client (JS chỉ thêm nếu cần)plaintextKết quả: site nhanh hơn, LightHouse 100/100 dễ hơn, ít JS hơn.
Islands Architecture#
Astro không hydrate toàn bộ trang. Nó chỉ hydrate các “island” — component React/Vue/Svelte riêng lẻ.
---
// src/pages/index.astro
import Header from '../components/Header.astro'
import LikeButton from '../components/LikeButton.tsx'
import Comments from '../components/Comments.svelte'
---
<!-- Static HTML — không JS -->
<Header />
<!-- Island — chỉ component này có JS -->
<LikeButton client:load />
<!-- Island khác — lazy hydrate khi scroll tới -->
<Comments client:visible />astroClient Directives#
<!-- Load JS ngay khi trang load -->
<Component client:load />
<!-- Load khi idle (cho component không critical) -->
<Component client:idle />
<!-- Load khi component visible trong viewport -->
<Component client:visible />
<!-- Load khi tương tác (click, hover) — cho modal/dropdown -->
<Component client:only="react" />
<!-- Tương tác media query -->
<Component client:media="(min-width: 768px)" />astroMỗi island là một JS bundle riêng — không load React cho đến khi cần.
Zero JS Mặc Định#
---
// Astro component — không runtime JS
const posts = await getCollection('blog')
---
<html>
<head><title>Blog</title></head>
<body>
<h1>Bài Viết</h1>
{posts.map(post => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
<p>{post.data.description}</p>
</article>
))}
</body>
</html>astroKết quả: HTML thuần. Không React runtime, không hydration, không JS bundle.
Content Collections#
Content Collections là cách quản lý Markdown/MDX content có schema validation.
// src/content.config.ts
import { defineCollection, z } from 'astro:content'
const blog = defineCollection({
schema: z.object({
title: z.string().max(60),
description: z.string().max(160),
publishDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
})
export const collections = { blog }typescript---
import { getCollection } from 'astro:content'
// Tất cả bài viết (trừ draft)
const posts = await getCollection('blog', ({ data }) => !data.draft)
// Sắp xếp theo ngày
const sorted = posts.sort(
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
)
---
{posts.map(post => (
<article>
<h2><a href={`/blog/${post.id}`}>{post.data.title}</a></h2>
<time>{post.data.publishDate.toDateString()}</time>
<p>{post.data.description}</p>
</article>
))}astro---
// [slug].astro — dynamic route
import { getCollection, getEntry } from 'astro:content'
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}))
}
const { post } = Astro.props
const { Content } = await post.render()
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>astroView Transitions#
View Transitions cho phép chuyển trang mượt mà không cần SPA:
---
// src/layouts/Layout.astro
---
<html>
<head>
<!-- Bật View Transitions cho toàn site -->
<meta name="view-transition" content="same-origin" />
</head>
<body>
<slot />
</body>
</html>astroHoặc dùng component Astro:
---
import { ViewTransitions } from 'astro:transitions'
---
<html>
<head>
<ViewTransitions />
</head>
</html>astroCustom Animation#
---
import { fade, slide } from 'astro:transitions'
---
<a href="/blog" transition:animate={slide}>Blog</a>
<div transition:animate={fade({ duration: '0.3s' })}>
<slot />
</div>astroKhông cần React Router, không JS bundle. Trình duyệt xử lý animation native.
Server Islands#
Astro cho phép render component dynamic trên server — không gửi JS cho client:
---
// Component render ở server, cache riêng
import Dashboard from '../components/Dashboard.astro'
---
<Layout>
<Dashboard server:defer />
<!-- Component render trên server, stream HTML khi sẵn sàng -->
</Layout>astroDùng cho: dữ liệu user-specific, API call nặng, cache riêng biệt.
Image Optimization#
---
import { Image } from 'astro:assets'
import hero from '../images/hero.png'
---
<!-- Tự động resize, format AVIF/WebP, lazy load -->
<Image
src={hero}
alt="Hero image"
widths={[400, 800, 1200]}
sizes="(max-width: 800px) 100vw, 800px"
loading="lazy"
decoding="async"
/>astroAstro tự động tối ưu: resize, format, responsive srcset.
Integrations#
Astro hỗ trợ UI framework phổ biến qua integrations:
npx astro add react # React
npx astro add vue # Vue
npx astro add svelte # Svelte
npx astro add solid # SolidJS
npx astro add tailwind # Tailwind CSS
npx astro add mdx # MDX support
npx astro add vercel # Vercel adapter
npx astro add sitemap # Sitemap
npx astro add partytown # Web WorkersbashBuild Output#
Static Site (Mặc định)#
npm run build
# dist/ — HTML, CSS, JS tĩnh
# Deploy lên Netlify, Vercel, Cloudflare Pages, S3...bashServer-Side Rendering#
// astro.config.ts
import vercel from '@astrojs/vercel/serverless'
export default defineConfig({
output: 'server',
adapter: vercel(),
})typescript# dist/server/ — server entry point
# API routes + dynamic pages render on requestbashHybrid#
// astro.config.ts
export default defineConfig({
output: 'hybrid', // Mặc định static, opt-in server
})typescript---
// pages/dashboard.astro — render trên server
export const prerender = false
---astroAstro vs Next.js#
| Next.js | Astro | |
|---|---|---|
| JS mặc định | React runtime (~40KB) | Zero JS |
| Rendering | SSG/SSR/ISR | Static/SSR/Hybrid |
| UI framework | React (Next) | React/Vue/Svelte/Solid (tuỳ chọn) |
| Content | File system hoặc CMS | Content Collections |
| Image | next/image | astro:assets |
| Routing | File-based + App Router | File-based |
| API routes | ✅ | ✅ (server mode) |
| View Transitions | ❌ (cần thư viện) | ✅ Built-in |
| Islands | ❌ (hydrate whole page) | ✅ Islands Architecture |
Chọn Next.js khi:#
- Web app phức tạp, cần nhiều client interaction
- Dashboard với real-time data
- Cần API routes phía backend
- Team quen React ecosystem
Chọn Astro khi:#
- Content site (blog, docs, landing page)
- E-commerce product pages
- Portfolio, marketing site
- Site cần load nhanh, SEO tốt
- Muốn dùng nhiều UI framework cùng lúc
Tổng Kết#
Astro sinh ra từ một insight đơn giản: hầu hết web là content, không phải app.
---
// Blog này chạy Astro — zero JS cho content, island cho interactive parts
const posts = await getCollection('blog')
---
{posts.map(post => (
<article>
<h2>{post.data.title}</h2>
<p>{post.data.description}</p>
</article>
))}
<!-- Only interactive part gets JS -->
<LikeButton client:visible />astro| Tính năng | Astro |
|---|---|
| Zero JS mặc định | ✅ |
| Islands Architecture | ✅ |
| Content Collections | ✅ Schema, type-safe |
| View Transitions | ✅ Built-in |
| Server Islands | ✅ |
| Image optimization | ✅ |
| Multi-framework (React/Vue/Svelte) | ✅ |
| Static + SSR + Hybrid | ✅ |
| Bundle size | Trang này ~0KB JS |
Nếu site bạn là content-first — blog, docs, marketing, landing page — Astro là lựa chọn tốt nhất hiện tại.