Astro
Astro 是"内容优先的 MPA 框架":默认输出零 JavaScript 的纯 HTML 页面,交互性以「岛屿」为单位按需附加,内容通过**内容层(Content Layer)**统一加载。它不抢 React/Vue 的 SPA 生态位,而是专注博客、文档、营销与电商这类"内容驱动 + 少量交互"站点。本文基于 Astro 5(2024-12 发布,当前主线);示例可复制进
npm create astro@latest模板直接运行。
一、定位与心智模型
1.1 为什么"默认零 JS"是卖点
| 对比项 | 传统 SPA(React/Vue 纯前端) | Astro |
|---|---|---|
| 页面产物 | 空 HTML + 整包 JS,客户端渲染 | 完整 HTML,SEO/首屏直达 |
| 水合 | 整棵树水合,交互才可用 | 只有"岛"水合,其余保持静态 |
| 内容来源 | 通常在客户端 fetch | 构建期/服务端读取,进 HTML |
| 适合 | 重交互应用(编辑器/仪表盘) | 内容站、静态页为主的站点 |
关键前提:一个页面大部分内容不需要 JS(正文、列表、SEO 结构),只有少数部件需要交互(导航、筛选、计数器)。SPA 把交互部件的能力成本摊给了整个页面;Astro 只把成本算在交互部件头上。
1.2 页面路由模型
Astro 不是 SPA:路由是真实文件 → 真实 URL,切换页面是浏览器导航(MPA)。因此天然 SSR/SSG、天然对搜索引擎与慢网络友好。
src/pages/
├── index.astro # → /
├── about.astro # → /about
├── blog/
│ ├── index.astro # → /blog
│ └── [slug].astro # → /blog/xxx(动态路由,getStaticPaths 生成路径)
└── rss.xml.ts # → /rss.xml(服务端端点)1.3 .astro 组件 = frontmatter + 模板
---
// ——frontmatter:构建/SSR 时运行的 JS/TS,三行短横线内——
const title = '我的博客'
const posts = await getCollection('blog') // 内容层查询(见 §3)
---
<html lang="zh-CN">
<head><meta charset="utf-8"><title>{title}</title></head>
<body>
<h1>{title}</h1>
<ul>
{posts.map((p) => <li><a href={`/blog/${p.id}`}>{p.data.title}</a></li>)}
</ul>
</body>
</html>- frontmatter 代码在服务器/构建期运行,可 await(访问 DB、fetch、读文件);
- 模板用类 JSX 表达式
{expr};逻辑靠map/三元,没有专用循环指令; .astro组件可以内嵌其他框架组件(§2)。
二、岛屿架构(Islands & Partial Hydration)
2.1 一个页面,两种代码
没有 client 指令的组件只参与构建期渲染,产出静态 HTML,不发送任何 JS;加了 client: 指令的组件才是"岛"——它的 HTML 先静态输出,再在浏览器端水合激活:
---
import StaticBanner from '../components/StaticBanner.astro'
import Counter from '../components/Counter.tsx' // 可以是 React 组件
import LikeButton from '../components/LikeButton.vue' // 也可以是 Vue 组件
---
<StaticBanner /> <!-- 纯静态,0 JS -->
<Counter client:load /> <!-- 岛:加载即水合 -->
<LikeButton client:visible /> <!-- 岛:滚动到可视才水合 -->2.2 client 指令矩阵(决定何时发 JS)
| 指令 | 时机 | 场景 |
|---|---|---|
| 无 | 永不发送 JS | 纯展示内容 |
client:load | 页面加载立即水合 | 首屏就要交互(顶部导航) |
client:idle | 浏览器空闲后水合 | 次优先交互 |
client:visible | 元素进入视口 | 折叠内容、页脚部件 |
client:media={...} | 匹配媒体查询后 | 移动端专属交互 |
client:only="react" | 仅客户端渲染(服务端不渲染) | 依赖 window 的组件 |
心智锚点:指令越"懒",静态占比越大,首屏越快。实践中 80% 的页面内容不该挂任何 client 指令。
2.3 集成主流框架(@astrojs/react / vue / svelte 等)
npx astro add react vue svelte # 自动装集成并写进 astro.config.mjs// astro.config.mjs
import { defineConfig } from 'astro/config'
import react from '@astrojs/react'
export default defineConfig({
integrations: [react()],
})同时装 React 与 Vue 也行——岛之间用 props 传原始值,不建议跨框架传复杂状态(每个岛自己的运行时自治)。多框架并存时的性能约束:每种框架的运行时只按需打包到使用它的岛屿。
三、内容层(Astro 5 的 Content Layer)
3.1 定义集合(src/content.config.ts)
Astro 5 用 Content Layer 取代旧内容集合 API:集合不再限定在 src/content/,来源可以是本地文件、CMS 或任意 API:
// 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/content/blog' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
})
export const collections = { blog }3.2 查询与渲染
---
import { getCollection, render } from 'astro:content'
import { Image } from 'astro:assets'
const posts = (await getCollection('blog'))
.filter((p) => !p.data.draft)
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
---
{posts.map(async (p) => {
const { Content } = await render(p) // 渲染 Markdown/MDX 正文
return (
<article>
<h2>{p.data.title}</h2>
<Content />
</article>
)
})}globloader 会处理 frontmatter、.md/.mdx正文与图片引用;- schema 用 zod 做构建期校验:字段写错、类型不匹配直接构建失败;
- 自定义 loader 只需实现
{ load() { return entries } }协议,即可接 CMS/数据库。
3.3 类型安全的环境变量(astro:env)
// astro.config.mjs
import { defineConfig, envField } from 'astro/config'
export default defineConfig({
env: {
schema: {
API_URL: envField.string({ context: 'server', access: 'public' }),
SECRET_KEY: envField.string({ context: 'server', access: 'secret' }),
},
},
})import { env } from 'astro:env'
console.log(env.API_URL) // 类型已知;未配置时构建期报错四、渲染模式与部署
4.1 SSG / SSR / 混合
| 模式 | 配置 | 特点 |
|---|---|---|
| 纯静态(默认) | 无需 adapter | astro build 输出 HTML 目录,任何静态托管可跑 |
| 全站 SSR | output: 'server' + adapter | 页面可访问动态数据(Astro.request 等) |
| 混合 | output: 'hybrid' + 页面级 export const prerender = false | 默认静态,个别页面动态 |
| 服务岛 | Server Islands | 静态壳里嵌"服务端动态渲染的缓存岛"(见 §4.3) |
// astro.config.mjs:SSR 需要安装对应 adapter
import node from '@astrojs/node'
export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }) })4.2 动态路由与 getStaticPaths
---
// src/pages/blog/[slug].astro
export function getStaticPaths() {
return [{ params: { slug: 'hello' } }, { params: { slug: 'world' } }]
}
---
<html><body>{Astro.params.slug}</body></html>静态模式下列出所有路径;SSR 模式下不写 getStaticPaths 即运行时按参数动态渲染。
4.3 Server Islands(Astro 5 引入)
静态页面里有些部件必须动态(购物车价、登录用户),又不希望整页改 SSR。服务岛把"静态页 + 动态区"缝合:静态 HTML 里嵌入 <astro-island> 占位,由缓存服务单独请求动态片段并填充:
静态主页 HTML(CDN 秒开)
└─ 头部导航(静态)
└─ <astro-island> 购物车徽标 ← 服务端单独渲染该片段(带缓存),客户端再水合注:Server Islands 在 Astro 5.0 以实验能力引入(需
experimental配置或已随版本转正,以官方文档为准)。演进方向与 React Server Components、Next.js 的「部分动态」同一条路:默认静态、动态按需。
五、工程与体验优化
5.1 图片(astro:assets)
---
import { Image } from 'astro:assets'
import hero from '../assets/hero.png'
---
<Image src={hero} alt="封面" widths={[640, 1024]} sizes="(max-width: 1024px) 100vw, 1024px"
format="avif" loading="lazy" />自动:响应式尺寸、AVIF/WebP 转换、懒加载、宽高占位防 CLS。
5.2 视图过渡(MPA 下的"类 SPA"切换)
---
import { ClientRouter } from 'astro:transitions'
---
<ClientRouter />页面切换走浏览器原生 View Transitions API:淡入淡出、共享元素过渡、transition:name 指定动画元素——不引入整页水合的代价就有类似 SPA 的体感。
5.3 构建与性能基线
npm create astro@latest # 脚手架(含模板选择)
npx astro add --help # 集成/适配器增删
npm run build # 产物在 dist/默认产物已经是"性能最优基线":零 JS 页面 + 岛按需加载 + 资源自动优化。主要继续优化项是字体/图片体积(配合 性能优化 章节)与"哪些组件真需要水合"的审查。
六、常见坑速查
- 前端框架思维带进来:页面级交互交给 client 指令后才水合,忘了加就是"点了没反应"(不是 bug,是没激活)——
client:指令别漏; - 在 .astro 模板里写客户端事件:模板的 JS 跑在服务端/构建期,直接
<div onclick="...">或组件内 state 逻辑不会在浏览器存在——需要交互就建"岛"组件(.tsx/.vue/.svelte + client 指令); - 把 Astro 组件(.astro)当可交互组件用:.astro 组件没有客户端运行时,无法携带客户端状态——交互组件要用集成框架写;
- 动态路由忘了 getStaticPaths:静态构建时缺路径直接构建报错(提示明显);SSR/hybrid 模式又不该写死——按渲染模式选;
- 内容集合 schema 不校验:draft 未过滤上线、日期格式不一、字段拼写错——Content Layer 的 zod schema 是构建期防线,一定要写;
- 旧内容集合 API 混用:Astro 4 的
src/content/+defineCollection(旧文件内)写法迁移到 5 要改成src/content.config.ts+ loader——两套 API 别混; - 视图过渡下组件状态重置/动画突兀:需要保留滚动的用
transition:persist,需要动画元素配transition:name; - SSR 模式忘装 adapter 或在静态托管上跑 server output:产物是 Node 服务不是静态目录,部署选型先对齐(§4.1)。
状态与参考
- 状态:已收录(2026-09-02,由前端领域规划清单「Astro|内容优先的岛屿架构、静态站点生成」转正式)。
- 版本:基于 Astro 5(2024-12-03 发布,Content Layer / Server Islands / astro:env 为 5.x 主线特性;Server Islands 的实验状态与转正节奏以官方文档为准)。示例为标准用法,可复制进
npm create astro@latest模板运行。 - 参考:Astro 官方文档、Astro 5.0 发布说明、Server Islands、Content Layer 指南。
- 阅读联动:岛屿架构与 SPA 框架差异对照前端领域概览与 React/Vue 3;内容站常见搭配 SvelteKit 的 SSG 模式对比;构建细节见构建工具。
下一步
- [ ] 镜像官方文档分篇(类似 Vue 3 栏目),把 Islands、Content Layer、渲染模式展开为独立子页
- [ ] 搭一个"文档站 + MDX + View Transitions + Server Islands 评论区"的完整示例并跑构建
- [ ] 对照 Next.js App Router 写"内容优先框架 vs 全栈框架"的选型对比
写作规范请参阅领域概览。