Astro 是一个现代 Web 框架,专为内容驱动型网站设计,默认不发送任何 JavaScript。凭借独特的岛屿架构,Astro 在构建时将页面渲染为静态 HTML,同时允许交互组件独立水合。无论你是构建博客、文档站点、营销页面还是电商前端,Astro 都能提供卓越的性能和支持 React、Vue、Svelte、Solid 的开发体验。
Astro 是一个内容优先的 Web 框架,默认零 JS、使用岛屿架构实现选择性交互、支持多种 UI 框架(React、Vue、Svelte),提供出色的 Core Web Vitals 分数。具备内容集合、视图过渡、SSR/SSG 模式,并与 Tailwind、MDX 和图片优化无缝集成。
- Astro 默认不发送 JavaScript,页面加载更快,Core Web Vitals 优于传统 SPA 框架。
- 岛屿架构允许在同一页面混合使用 React、Vue、Svelte 和 Solid 组件,各自独立水合。
- 内容集合通过 Zod 验证提供类型安全的 Markdown、MDX、YAML 和 JSON 数据访问。
- 视图过渡 API 无需客户端路由或 JavaScript 框架即可实现平滑的页面动画。
- Astro 同时支持静态站点生成(SSG)和按需服务端渲染(SSR),适配 Vercel、Netlify 和 Cloudflare。
- Astro DB 提供基于 LibSQL 的内置 SQL 数据库,具有完整的 TypeScript 类型安全。
什么是 Astro,它是如何工作的?
Astro 采用了与 Next.js 或 Remix 等基于 React 的框架截然不同的方法。Astro 构建的是一个 HTML 网站,可以选择性地包含 JavaScript,而不是构建一个渲染 HTML 的 JavaScript 应用。
Astro 使用独特的组件格式(.astro 文件),将 frontmatter 脚本部分与 HTML 模板结合。frontmatter 在构建时运行,永远不会到达浏览器。
---
// src/pages/index.astro
import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
import Counter from "../components/Counter.tsx";
// This runs at build time (or request time in SSR)
// It never reaches the browser
const response = await fetch("https://api.example.com/posts");
const posts = await response.json();
---
<Layout title="My Blog">
<h1>Latest Posts</h1>
<!-- Static HTML: zero JavaScript -->
{posts.map((post) => (
<Card title={post.title} url={"/blog/" + post.slug} />
))}
<!-- Interactive island: only this loads JS -->
<Counter client:visible initialCount={0} />
</Layout>岛屿架构详解
岛屿架构是 Astro 的核心创新。在传统 SPA 中,整个页面是一个 JavaScript 应用。在 Astro 中,页面是静态 HTML,带有独立水合的交互"岛屿"。
你可以通过客户端指令控制每个岛屿的水合时机和方式。
客户端水合指令
Astro 提供多种客户端指令来控制岛屿水合时机:
- client:load - 页面加载时立即水合。用于首屏交互元素。
- client:idle - 浏览器空闲时水合。用于低优先级交互元素。
- client:visible - 元素进入视口时水合。用于首屏以下元素。
- client:media - CSS 媒体查询匹配时水合。用于仅移动端或桌面端的交互。
- client:only - 跳过服务端渲染,仅在客户端渲染。用于依赖浏览器 API 的组件。
---
// src/pages/dashboard.astro
import Header from "../components/Header.astro";
import SearchBar from "../components/SearchBar.tsx";
import Chart from "../components/Chart.svelte";
import Newsletter from "../components/Newsletter.vue";
---
<!-- Pure HTML, zero JS -->
<Header />
<!-- Hydrate immediately for above-the-fold search -->
<SearchBar client:load placeholder="Search tools..." />
<!-- Hydrate when visible (below the fold) -->
<Chart client:visible data={chartData} />
<!-- Hydrate when idle (low priority) -->
<Newsletter client:idle />
<!-- Only hydrate on mobile screens -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Client-only: skip SSR for browser-API-dependent code -->
<MapWidget client:only="react" />内容集合与类型安全
内容集合是 Astro 最强大的功能之一,提供结构化的方式来组织、验证和查询内容,具有完整的 TypeScript 类型安全。
定义集合模式后,Astro 自动生成 TypeScript 类型。如果 Markdown frontmatter 字段缺失或类型错误,你会在构建时收到错误。
// src/content.config.ts
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const blog = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/data/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
author: z.string().default("Anonymous"),
}),
});
const docs = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/data/docs" }),
schema: z.object({
title: z.string(),
section: z.enum(["getting-started", "guides", "reference"]),
order: z.number(),
}),
});
export const collections = { blog, docs };---
// src/pages/blog/[slug].astro
import { getCollection, getEntry, render } from "astro:content";
import Layout from "../../layouts/Layout.astro";
// Generate static pages for all non-draft blog posts
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => {
return data.draft !== true;
});
return posts.map((post) => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await render(post);
---
<Layout title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<p>{post.data.description}</p>
<time datetime={post.data.pubDate.toISOString()}>
{post.data.pubDate.toLocaleDateString()}
</time>
<div class="tags">
{post.data.tags.map((tag) => <span>{tag}</span>)}
</div>
<Content />
</article>
</Layout>Astro 组件 vs 框架组件
Astro 有自己的组件格式(.astro 文件),专门优化用于 HTML 输出。你也可以使用 React、Vue、Svelte 等框架组件。
Astro 组件(.astro)
Astro 组件是 HTML 优先的模板,带有 JavaScript frontmatter 部分。它们完全在构建时渲染,不产生 JavaScript 输出。
---
// src/components/Card.astro
interface Props {
title: string;
description: string;
url: string;
tags?: string[];
}
const { title, description, url, tags = [] } = Astro.props;
---
<a href={url} class="card">
<h3>{title}</h3>
<p>{description}</p>
{tags.length > 0 && (
<div class="tags">
{tags.map((tag) => <span class="tag">{tag}</span>)}
</div>
)}
</a>
<style>
.card {
display: block;
padding: 1.5rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
text-decoration: none;
transition: box-shadow 0.2s;
}
.card:hover {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>框架组件(React、Vue、Svelte)
需要客户端交互时使用框架组件。添加 client 指令后,Astro 在服务端渲染它们并在客户端水合。
// src/components/Counter.tsx (React island)
import { useState } from "react";
export default function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<button onClick={() => setCount(count - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}<!-- src/components/Toggle.svelte (Svelte island) -->
<script>
let isOpen = false;
</script>
<button on:click={() => isOpen = !isOpen}>
{isOpen ? "Close" : "Open"}
</button>
{#if isOpen}
<div class="panel">
<slot />
</div>
{/if}---
// Using multiple frameworks on one page
import Counter from "../components/Counter.tsx"; // React
import Toggle from "../components/Toggle.svelte"; // Svelte
import Carousel from "../components/Carousel.vue"; // Vue
---
<Counter client:load initialCount={5} />
<Toggle client:visible>Content inside Svelte slot</Toggle>
<Carousel client:idle images={images} />静态站点生成与服务端渲染
Astro 支持两种主要输出模式:static(默认)和 server。还可以使用 hybrid 模式在同一项目中混合静态和服务端渲染页面。
静态模式(默认)
静态模式下,Astro 在构建时预渲染每个页面为 HTML。产生的 HTML 文件可部署到任何静态托管提供商。
// astro.config.mjs - Static mode (default)
import { defineConfig } from "astro/config";
export default defineConfig({
output: "static", // This is the default
site: "https://example.com",
});服务端模式(SSR)
服务端模式下,页面在每次请求时在服务器上渲染,支持用户认证、个性化内容和数据库查询等动态功能。
// astro.config.mjs - Server mode
import { defineConfig } from "astro/config";
import vercel from "@astrojs/vercel";
export default defineConfig({
output: "server",
adapter: vercel(),
});混合模式
混合模式允许大部分页面静态预渲染,同时将特定页面选择为服务端渲染。
// astro.config.mjs - Hybrid mode
import { defineConfig } from "astro/config";
import netlify from "@astrojs/netlify";
export default defineConfig({
output: "hybrid",
adapter: netlify(),
});
// --- src/pages/about.astro (pre-rendered at build time) ---
// Pages are static by default in hybrid mode
---
<h1>About Us</h1>
<p>This page is pre-rendered at build time.</p>
---
// --- src/pages/dashboard.astro (server-rendered) ---
// Opt into server rendering for this page
export const prerender = false;
---
const user = await getUser(Astro.cookies.get("session"));
---
<h1>Welcome, {user.name}</h1>视图过渡与页面动画
Astro 内置支持视图过渡 API,无需客户端路由即可实现平滑的页面过渡动画。
你可以按元素自定义过渡效果,跨导航保持状态,甚至使用 transition:name 在页面之间动画化元素。
---
// src/layouts/Layout.astro
import { ViewTransitions } from "astro:transitions";
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<nav transition:persist>
<!-- Nav persists across navigations -->
<a href="/">Home</a>
<a href="/blog">Blog</a>
</nav>
<main transition:animate="slide">
<slot />
</main>
</body>
</html>---
// Custom transitions with transition:name
// Blog listing page
---
{posts.map((post) => (
<a href={"/blog/" + post.slug}>
<img
src={post.image}
transition:name={"hero-" + post.slug}
/>
<h2 transition:name={"title-" + post.slug}>
{post.title}
</h2>
</a>
))}
// --- Blog detail page ---
// Same transition:name creates a shared element transition
<img
src={post.image}
transition:name={"hero-" + post.slug}
/>
<h1 transition:name={"title-" + post.slug}>
{post.title}
</h1>Astro DB 与数据层
Astro DB 是为内容站点设计的内置 SQL 数据库,基于 LibSQL,提供完整类型化的数据库和简单的查询 API。
数据层将内容集合扩展到任何数据源,可以从外部 API、CMS 平台或数据库拉取数据。
// db/config.ts - Define your database schema
import { defineDb, defineTable, column } from "astro:db";
const Comment = defineTable({
columns: {
id: column.number({ primaryKey: true }),
postSlug: column.text(),
author: column.text(),
body: column.text(),
createdAt: column.date({ default: "NOW" }),
},
});
const Like = defineTable({
columns: {
id: column.number({ primaryKey: true }),
postSlug: column.text(),
count: column.number({ default: 0 }),
},
});
export default defineDb({
tables: { Comment, Like },
});---
// Querying Astro DB in a page
import { db, Comment, eq } from "astro:db";
const { slug } = Astro.params;
const comments = await db
.select()
.from(Comment)
.where(eq(Comment.postSlug, slug))
.orderBy(Comment.createdAt);
---
<h2>Comments</h2>
{comments.map((c) => (
<div class="comment">
<strong>{c.author}</strong>
<p>{c.body}</p>
<time>{c.createdAt.toLocaleDateString()}</time>
</div>
))}中间件与 API 端点
Astro 支持中间件来拦截请求,可以修改请求上下文、添加认证检查、重定向用户或设置响应头。
// src/middleware.ts
import { defineMiddleware, sequence } from "astro:middleware";
const auth = defineMiddleware(async (context, next) => {
const token = context.cookies.get("session")?.value;
if (context.url.pathname.startsWith("/dashboard")) {
if (!token) {
return context.redirect("/login");
}
const user = await verifyToken(token);
context.locals.user = user;
}
return next();
});
const logging = defineMiddleware(async (context, next) => {
const start = Date.now();
const response = await next();
const duration = Date.now() - start;
console.log(context.url.pathname + " - " + duration + "ms");
return response;
});
export const onRequest = sequence(logging, auth);API 端点
Astro 可以在页面旁提供 JSON API 端点。在 src/pages 目录创建导出 HTTP 方法处理程序的 .ts 文件。
// src/pages/api/comments.ts
import type { APIRoute } from "astro";
import { db, Comment } from "astro:db";
export const GET: APIRoute = async ({ url }) => {
const slug = url.searchParams.get("slug");
if (!slug) {
return new Response(
JSON.stringify({ error: "slug is required" }),
{ status: 400 }
);
}
const comments = await db
.select()
.from(Comment)
.where(eq(Comment.postSlug, slug));
return new Response(JSON.stringify(comments), {
headers: { "Content-Type": "application/json" },
});
};
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const { postSlug, author, content } = body;
await db.insert(Comment).values({
postSlug,
author,
body: content,
});
return new Response(
JSON.stringify({ success: true }),
{ status: 201 }
);
};集成:Tailwind、MDX 和图片优化
Astro 拥有丰富的官方和社区集成生态。最常用的包括 Tailwind CSS、MDX 和内置图片优化。
Tailwind CSS 集成
Astro 对 Tailwind CSS 有一流支持,集成自动处理配置、PostCSS 设置和未使用样式的清除。
# Install Tailwind integration
npx astro add tailwind
# This automatically:
# 1. Installs @astrojs/tailwind and tailwindcss
# 2. Adds the integration to astro.config.mjs
# 3. Creates a tailwind.config.mjs file// astro.config.mjs
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
export default defineConfig({
integrations: [
tailwind({
// Apply base styles automatically
applyBaseStyles: true,
// Path to custom config
configFile: "./tailwind.config.mjs",
}),
],
});MDX 集成
MDX 允许在 Markdown 文件中使用 JSX 组件,可以在内容中嵌入交互组件、图表和自定义布局。
# Install MDX integration
npx astro add mdx
# Now you can use .mdx files in content collections
# and import components directly in Markdown---
// src/data/blog/interactive-post.mdx
title: "Interactive Tutorial"
pubDate: 2026-01-15
---
import CodePlayground from "../../components/CodePlayground.tsx";
import Chart from "../../components/Chart.svelte";
# Interactive Tutorial
Here is a live code playground:
<CodePlayground client:visible code="console.log(42)" />
And a dynamic chart:
<Chart client:idle type="bar" data={[10, 20, 30]} />图片优化
Astro 内置图片优化服务,自动调整大小、转换格式和优化图片。Image 组件生成响应式 srcset 属性。
---
// Using the Image component
import { Image } from "astro:assets";
import heroImage from "../assets/hero.png";
---
<!-- Automatic optimization: resize, format, lazy load -->
<Image
src={heroImage}
alt="Hero banner"
width={1200}
height={600}
format="webp"
quality={80}
/>
<!-- Remote images with dimensions -->
<Image
src="https://example.com/photo.jpg"
alt="Remote photo"
width={800}
height={400}
inferSize
/>
<!-- Picture component for art direction -->
import { Picture } from "astro:assets";
<Picture
src={heroImage}
formats={["avif", "webp"]}
alt="Responsive hero"
widths={[400, 800, 1200]}
sizes="(max-width: 800px) 100vw, 800px"
/>部署到 Vercel、Netlify 和 Cloudflare
Astro 通过官方适配器支持部署到所有主要托管平台。
部署到 Vercel
# Install the Vercel adapter
npx astro add vercel
# astro.config.mjs
import { defineConfig } from "astro/config";
import vercel from "@astrojs/vercel";
export default defineConfig({
output: "server", // or "hybrid"
adapter: vercel({
// Enable ISR with 60-second revalidation
isr: {
expiration: 60,
},
// Enable image optimization
imageService: true,
// Enable Web Analytics
webAnalytics: { enabled: true },
}),
});部署到 Netlify
# Install the Netlify adapter
npx astro add netlify
# astro.config.mjs
import { defineConfig } from "astro/config";
import netlify from "@astrojs/netlify";
export default defineConfig({
output: "server",
adapter: netlify({
// Use edge functions for faster cold starts
edgeMiddleware: true,
// Cache static assets
cacheOnDemandPages: true,
}),
});部署到 Cloudflare Pages
# Install the Cloudflare adapter
npx astro add cloudflare
# astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
export default defineConfig({
output: "server",
adapter: cloudflare({
mode: "directory",
// Access Cloudflare bindings (KV, D1, R2)
platformProxy: {
enabled: true,
},
}),
});
// Access Cloudflare bindings in your pages
// src/pages/api/data.ts
export const GET: APIRoute = async ({ locals }) => {
const { runtime } = locals;
const kv = runtime.env.MY_KV_NAMESPACE;
const value = await kv.get("key");
return new Response(JSON.stringify({ value }));
};性能优化与最佳实践
Astro 开箱即用就有出色的性能,但还有一些技巧可以进一步优化。
- 首屏以下交互组件使用 client:visible 而非 client:load 来延迟 JavaScript 加载。
- 使用内容集合进行构建时验证,而非运行时数据获取。
- 使用内置 Image 组件自动优化图片并生成响应式 srcset。
- 启用视图过渡实现平滑导航,无需加载客户端路由框架。
- 非交互 UI 优先使用 Astro 组件而非框架组件,消除 JavaScript 开销。
- 使用混合渲染模式预渲染静态页面,保持动态页面服务端渲染。
- 将大型交互组件拆分为更小的岛屿,减少单个包大小。
- 配置预取以改善感知性能。
- 使用 Astro 内置资源处理获得自动哈希和缓存。
- 使用 astro build --profile 分析构建输出,识别大包和优化机会。
// Performance-optimized astro.config.mjs
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import compress from "astro-compress";
import sitemap from "@astrojs/sitemap";
export default defineConfig({
site: "https://example.com",
integrations: [
tailwind(),
sitemap(),
compress({
CSS: true,
HTML: true,
JavaScript: true,
Image: true,
SVG: true,
}),
],
prefetch: {
prefetchAll: false,
defaultStrategy: "viewport",
},
image: {
service: { entrypoint: "astro/assets/services/sharp" },
remotePatterns: [
{ protocol: "https", hostname: "**.example.com" },
],
},
vite: {
build: {
cssMinify: "lightningcss",
rollupOptions: {
output: {
manualChunks: {
react: ["react", "react-dom"],
},
},
},
},
},
});常见问题
Astro 用来做什么?
Astro 主要用于内容驱动型网站,包括博客、文档站点、营销页面、作品集和电商前端。其零 JavaScript 默认方式使其成为性能和 SEO 至关重要的站点的理想选择。
Astro 的岛屿架构如何工作?
Astro 默认将每个页面渲染为静态 HTML。需要交互时,添加 client 指令使框架组件成为独立的岛屿,各自单独水合和加载 JavaScript。
可以在 Astro 中使用 React 组件吗?
可以。Astro 支持 React、Vue、Svelte、Solid、Preact 和 Lit 组件,甚至可以在同一页面混合多个框架。
Astro 比 Next.js 好吗?
Astro 和 Next.js 服务于不同的用例。Astro 擅长内容密集型网站,Next.js 更适合复杂的交互式 Web 应用。
Astro 内容集合如何工作?
内容集合在 src/content 目录中组织文件,使用 Zod 定义模式,Astro 自动生成 TypeScript 类型,提供构建时验证。
Astro 支持服务端渲染吗?
支持。Astro 同时支持 SSG 和 SSR,使用 server 或 hybrid 输出模式并安装部署平台的适配器即可。
什么是 Astro 视图过渡?
Astro 视图过渡使用浏览器的 View Transitions API 为页面导航添加动画,无需客户端路由器。
如何部署 Astro 站点?
静态站点使用 astro build 构建后部署 dist 目录。SSR 站点安装相应适配器后部署到对应平台。