Astro 默认零 JavaScript,核心是群岛架构(组件按需水合)和类型安全的内容集合,兼具极致性能和现代开发体验。
群岛架构与水合策略
非交互内容输出纯静态 HTML,只有标记 client:load/visible/idle/only 的组件才会加载 JS 并水合。同一页面可混合 React/Svelte/Vue 组件互不冲突。
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string().max(80),
description: z.string().max(200),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
cover: z.object({ src: z.string(), alt: z.string() }).optional(),
draft: z.boolean().default(false),
category: z.enum(['tutorial','technology','essays','translation']),
}),
});
const projects = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
name: z.string(), summary: z.string(),
hero: image(), stack: z.array(z.string()),
repo: z.string().url().optional(), live: z.string().url().optional(),
featured: z.boolean().default(false),
}),
});
export const collections = { blog, projects };
// src/pages/blog/[...slug].astro
/* ---
import { getCollection, type CollectionEntry } from 'astro:content';
import { Image } from 'astro:assets';
import LikeCounter from '../../components/LikeCounter.svelte';
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => import.meta.env.DEV || !data.draft);
return posts.map(p => ({ params: { slug: p.slug }, props: { post: p } }));
}
const { post } = Astro.props as { post: CollectionEntry<'blog'> };
const { Content } = await post.render();
--- ---
<article>
<header>
<h1>{post.data.title}</h1>
{post.data.cover && <Image src={post.data.cover.src} alt={post.data.cover.alt} widths={[400,800,1200]} />}
</header>
<Content />
<LikeCounter client:idle postSlug={post.slug} />
</article>
*/
Content Collections 类型安全
在 content/config.ts 中用 Zod 为集合定义 schema,getCollection 返回内容时自动校验 frontmatter,不匹配直接报错。支持图片自动优化。
| 水合指令 | 触发时机 | 体积开销 | 适合组件 |
|---|---|---|---|
| (默认) | 永远不水合 | 0 JS | 纯展示卡片/正文 |
| client:load | 页面加载即水合 | 最高 | 导航/核心交互 |
| client:idle | 浏览器空闲时 | 中低 | 点赞/次要交互 |
| client:visible | 滚动进入视口 | 极低 | 页脚/长列表后项 |
最佳实践
默认先不写 client:*,确定需要交互再添加。内容集合 schema 严格化避免脏数据。用 getStaticPaths + Slug 预渲染所有动态路由。