要点
开一个新系列,讲 Astro。定位一句话说完:为内容而生的站点框架——文档、博客、营销页、知识库,凡是”页面多、交互少、要 SEO”的站,就是它的主场。当前主版本是 5.x(5.0 于 2024 年 12 月发布,撰写时最新为 5.2.x,时间线细节见文末核实声明)。看完这篇你能回答四个问题:Islands 架构为什么对内容站是质变、Astro 和 Next.js / Nuxt 的定位差异在哪、content collections 怎么把内容当代码管、以及你已经会的那点 React / Vue 怎么直接复用进来。
为什么内容站值得一个专门的框架
先用一个多数人碰过的场景破题:拿应用框架写文档站,第一周一切顺利,第二周开始不对劲——页面没几个交互,浏览器却在为每页下载并执行一整套框架运行时,手机首屏肉眼可见地慢半拍。问题不在框架实现,在定位错位:应用框架为”每页都是应用”设计,内容站九成面积是静态的,为一成交互背一整套运行时,怎么算都亏。
Astro 的回答很直接:默认输出纯静态 HTML,产物里没有框架运行时,浏览器默认下载的 JavaScript 是零——官方文档口径是 “ships zero JavaScript by default”,这不是营销话术,是默认行为:.astro 组件全部在构建期渲染成 HTML,只有显式声明”这块要交互”,对应组件才带着运行时进入产物。内容站拿这个默认值,等于起点就站在正确的一边。
Islands 架构:默认零 JS,按需注水
Islands 架构(孤岛架构)是 Astro 的核心机制,理解了它,后面所有 API 都顺理成章。一张页面在 Astro 眼里是:一大片静态 HTML 海洋,加上零星几个交互孤岛(islands)。孤岛之外的 everything 都是构建期定稿的 HTML;孤岛之内才发生注水(hydration,即浏览器执行 JS 把静态 HTML 变成可交互组件)。
为什么这个结构对内容站是质变?注水成本与组件树大小成正比,而内容站的组件树九成是导航、正文、页脚这些不需要交互的东西。孤岛架构把决定权交回给你:页面里有多少 JS,取决于你声明了几个岛,而不是页面有多大。React 组件为什么动辄整树重渲染、怎么避免白算,站内《React Hooks 心智模型》有展开;孤岛架构等于从框架层面绕开了这整类问题。
声明孤岛的方式是指令(directive),就这几个,值得整表记住:
| 指令 | 注水时机 | 典型用途 |
|---|---|---|
| client:load | 页面加载立即 | 始终可见且必须可交互的组件(如顶栏搜索框) |
| client:idle | 浏览器空闲时 | 不在首屏关键路径上的组件(如评论区入口) |
| client:visible | 滚动进入视口时 | 页面下方的组件(如页脚订阅表单) |
| client:media | 匹配媒体查询时 | 只在移动端/桌面端出现的交互件 |
| client:only | 跳过构建期渲染,纯客户端 | 依赖浏览器 API、不能在服务端渲染的组件 |
不带任何指令的组件就是纯静态:渲染成 HTML,零 JS。反模式点名:给布局组件挂 client:load。导航、页脚这类”看起来是组件、实际不需要状态”的东西,正确待遇是不注水——一个挂了 client:load 的布局,会把整个页面拖回”整页注水”的老路,孤岛架构的好处当场清零。
和 Next.js / Nuxt 的定位差异
三者经常被放在一起比,但它们回答的不是同一个问题。Next.js 和 Nuxt 是应用框架(前者绑定 React,后者绑定 Vue),假设你的站点”有大量应用行为”;Astro 是内容框架,假设你的站点”绝大部分是内容”。差异摆成表:
| 维度 | Astro | Next.js | Nuxt |
|---|---|---|---|
| 定位 | 内容站框架 | React 全栈应用框架 | Vue 全栈应用框架 |
| 默认 JS 产物 | 零,按岛增加 | 交互部分带 React 运行时 | 交互部分带 Vue 运行时 |
| 默认渲染模式 | 静态(SSG),SSR 按需加 adapter | SSR / ISR 为主,静态可选 | SSR 为主,模式可切 |
| UI 框架 | 不绑定,官方集成 React / Vue / Svelte / Solid / Preact / Alpine / Lit | 仅 React | 仅 Vue |
| 内容建模 | content collections 内置 | MDX + 文件系统,或接 CMS | @nuxt/content 模块 |
| 服务端能力 | 可选(adapter 开启) | 核心能力(API 路由、Server Actions) | 核心能力(Nitro) |
| 典型场景 | 文档、博客、营销页、内容为主的站 | 应用与内容混合、重交互产品 | 应用与内容混合、重交互产品 |
选择逻辑一句话:看这个站”应用部分”占几成。九成是内容、一成是交互(搜索框、主题切换、评论区),Astro 的默认值帮你省掉最多的东西;五五开、有登录态和大量客户端状态,Next.js / Nuxt 的全栈能力才是正解——站内《用 Next.js 做一个真的能被搜到的内容站》就是后一种路线的完整实战,两篇对读能看清两种路线的形态差异。另注:Next.js 也能把内容页做到近乎零 JS,但那是主动争取的结果;Astro 里它是默认值——框架选型,很多时候就是在选默认值站在哪边。
上手:创建工程
Node 用 20 或更新版本(Astro 5 的基线,官方文档口径)。创建命令:
npm create astro@latest my-docs
1. 模板选 Basics 即可;是否加 TypeScript 选 strict——内容站的 schema 校验收益全靠它
cd my-docs
npm run dev # 默认 http://localhost:4321
工程结构比应用框架简单一档:src/pages/ 下一个文件就是一个路由(src/pages/about.astro 对应 /about),src/components/ 放组件,src/content/ 放内容集合。没有强制布局约定,布局就是一个普通组件。
Content Collections:把内容当代码管理
内容站的真正主体是 markdown 文件,”怎么管这些文件”就是内容站的数据层问题。Astro 的答案是 content collections(内容集合):每个集合一份 schema,frontmatter 字段拼错、类型不对,构建期就报错,而不是渲染出一张坏页面。这是 Astro 对内容站最实在的内置能力,也是它和”应用框架里自己搭 MDX 管线”拉开差距的地方。
现行 API 是 Astro 5 引入的 Content Layer(内容层),配置文件固定叫 src/content.config.ts;旧版 legacy collections(type: 'content' 写法)已标记弃用,老教程照抄虽还能跑,但建议直接按 Content Layer 写:
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const docs = defineCollection({
// glob loader:内容文件不再被锁死在 src/content/ 下,base 指哪都行
loader: glob({ pattern: '**/*.md', base: './src/content/docs' }),
schema: z.object({
title: z.string(),
section: z.string(),
order: z.number().default(99), // 没写就给默认值,目录排序用
}),
});
export const collections = { docs };
页面侧查询与渲染,注意两个 5.0 起 API 的细节:条目的标识字段是 id(旧版的 slug 已被 id 取代);渲染改用从 astro:content 导入的 render(entry)(旧写法 entry.render() 已被取代):
---
// src/pages/docs/[...id].astro —— 文件路径即路由,方括号是动态段
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const docs = await getCollection('docs');
return docs.map((entry) => ({
params: { id: entry.id }, // slug → id 是 5.0 的破坏性变更,老代码集中改这里
props: { entry },
}));
}
const { entry } = Astro.props;
const { title } = entry.data; // 经过 schema 校验,字段拼错构建期就炸
const { Content } = await render(entry);
---
<article>
<h1>{title}</h1>
<Content />
</article>
反模式点名:绕过 collections 用 import.meta.glob 散取 markdown。小站能跑,但 frontmatter 从此没有校验——某篇的 order 写成字符串,报错位置会出现在运行中的页面而不是构建日志里。内容站的坏数据要在构建期拦住,这是 collections 存在的全部理由。
集成任意 UI 框架组件
Astro 不绑定 UI 框架,但不是”不让你用”,恰恰相反:官方集成让 React、Vue、Svelte、Solid、Preact、Alpine、Lit 的组件都能直接进 Astro 页面,混用都行。加集成的命令:
npx astro add react # 换成 vue / svelte / solid / preact / alpine / lit 同理
---
// src/pages/index.astro
import SearchBox from '../components/SearchBox'; // 一个 .tsx 的 React 组件
import ThemeToggle from '../components/ThemeToggle.vue'; // 和一个 Vue 组件,同页共存
---
<!-- 不带 client: 指令:两个组件都在构建期渲染成静态 HTML,浏览器零 JS -->
<SearchBox client:visible />
<ThemeToggle client:load />
一个容易误解的点:不带指令时组件仍是”渲染过的”——构建期在 Node 里跑一遍,输出 HTML,只是不注水,浏览器拿到的就是成品。对写惯 React 的人,这套心智是”组件默认是模板,指令才升级为应用”;React 的渲染模型在岛上原样适用,只是执行时机挪到了构建期。Vue 背景同理,站内Vue 系列首篇起的知识都能直接搬进岛里。
顺带交代 view transitions(视图过渡)的现状:现行组件是 astro:transitions 里的 <ClientRouter />,负责软导航(不整页刷新)与过渡动画;早期版本叫 ViewTransitions,早已改名——看旧教程时认准 ClientRouter 这个名字。
常见问题
Q1: 不会 React / Vue 能学 Astro 吗
能。.astro 语法自成一体且更接近 HTML:顶部一对 --- 写服务端逻辑,下面直接写标签。React / Vue 只在”岛需要真交互”时才用得上,而内容站里这种地方很少。
Q2: 静态站是不是做不了动态内容
不是非黑即白。Astro 默认静态输出,加一个 adapter(Node、Vercel、Cloudflare 等官方适配器)就能开 SSR,还能按路由逐条指定渲染模式——文档主体静态、状态页动态,是常见组合。
Q3: 已经在用 Next.js / Nuxt 写内容站,要迁吗
多数情况不急。值得动手的信号只有两个:JS 产物明显拖累移动端体验,或内容管线成了日常摩擦。只为”听说更快”就迁移,大概率是给自己找事。
练习与实践
练习一:建一个含两篇 markdown 的 mini 文档站,把其中一篇的 order 字段删掉,跑构建,观察是报错还是取默认值(答案是后者,.default() 的意义)。练习二:把一个 React 搜索框挂上 client:visible,用开发者工具 Network 面板对比挂与不挂的 JS 体积。验收标准:能对页面里任何一个组件说出”它注不注水、什么时候注、为什么”。
延伸阅读
本篇只搭认知。下一篇《用 Astro 做一个极速文档站》是完整实战:内容站选 Astro 不选 Next 的决策链、content collections 建 schema、搜索方案怎么选、静态托管部署,一篇走完。基础不牢的,先回《React Hooks 心智模型》或Vue 系列首篇补课。
版本核实(2025-02-01 撰写时检索):Astro 当前主版本为 5.x,5.0 发布于 2024-12-03,撰写时最新为 5.2.x(GitHub releases 确认);legacy content collections(type: 'content'/type: 'data')已弃用,新项目一律按 Content Layer 写;条目标识 slug 已改为 id;Node 基线为 18.17+ / 20+,Node 22 可用;collections schema 校验用 Zod v3(astro:content 直接导出 z)。Content Layer 的 glob() loader、src/content.config.ts、render(entry)、ClientRouter 均为官方文档现行 API。安装命令不钉版本,一切以 docs.astro.build 为准。