Skip to content

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 + 模板

astro
---
// ——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 先静态输出,再在浏览器端水合激活:

astro
---
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 等)

sh
npx astro add react vue svelte      # 自动装集成并写进 astro.config.mjs
js
// 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:

ts
// 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 查询与渲染

astro
---
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>
  )
})}
  • glob loader 会处理 frontmatter、.md/.mdx 正文与图片引用;
  • schema 用 zod 做构建期校验:字段写错、类型不匹配直接构建失败;
  • 自定义 loader 只需实现 { load() { return entries } } 协议,即可接 CMS/数据库。

3.3 类型安全的环境变量(astro:env)

ts
// 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' }),
    },
  },
})
ts
import { env } from 'astro:env'
console.log(env.API_URL)          // 类型已知;未配置时构建期报错

四、渲染模式与部署

4.1 SSG / SSR / 混合

模式配置特点
纯静态(默认)无需 adapterastro build 输出 HTML 目录,任何静态托管可跑
全站 SSRoutput: 'server' + adapter页面可访问动态数据(Astro.request 等)
混合output: 'hybrid' + 页面级 export const prerender = false默认静态,个别页面动态
服务岛Server Islands静态壳里嵌"服务端动态渲染的缓存岛"(见 §4.3)
js
// astro.config.mjs:SSR 需要安装对应 adapter
import node from '@astrojs/node'
export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }) })

4.2 动态路由与 getStaticPaths

astro
---
// 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)

astro
---
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"切换)

astro
---
import { ClientRouter } from 'astro:transitions'
---
<ClientRouter />

页面切换走浏览器原生 View Transitions API:淡入淡出、共享元素过渡、transition:name 指定动画元素——不引入整页水合的代价就有类似 SPA 的体感。

5.3 构建与性能基线

sh
npm create astro@latest          # 脚手架(含模板选择)
npx astro add --help             # 集成/适配器增删
npm run build                    # 产物在 dist/

默认产物已经是"性能最优基线":零 JS 页面 + 岛按需加载 + 资源自动优化。主要继续优化项是字体/图片体积(配合 性能优化 章节)与"哪些组件真需要水合"的审查。

六、常见坑速查

  1. 前端框架思维带进来:页面级交互交给 client 指令后才水合,忘了加就是"点了没反应"(不是 bug,是没激活)——client: 指令别漏;
  2. 在 .astro 模板里写客户端事件:模板的 JS 跑在服务端/构建期,直接 <div onclick="..."> 或组件内 state 逻辑不会在浏览器存在——需要交互就建"岛"组件(.tsx/.vue/.svelte + client 指令);
  3. 把 Astro 组件(.astro)当可交互组件用:.astro 组件没有客户端运行时,无法携带客户端状态——交互组件要用集成框架写;
  4. 动态路由忘了 getStaticPaths:静态构建时缺路径直接构建报错(提示明显);SSR/hybrid 模式又不该写死——按渲染模式选;
  5. 内容集合 schema 不校验:draft 未过滤上线、日期格式不一、字段拼写错——Content Layer 的 zod schema 是构建期防线,一定要写;
  6. 旧内容集合 API 混用:Astro 4 的 src/content/ + defineCollection(旧文件内)写法迁移到 5 要改成 src/content.config.ts + loader——两套 API 别混;
  7. 视图过渡下组件状态重置/动画突兀:需要保留滚动的用 transition:persist,需要动画元素配 transition:name
  8. 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 IslandsContent Layer 指南
  • 阅读联动:岛屿架构与 SPA 框架差异对照前端领域概览React/Vue 3;内容站常见搭配 SvelteKit 的 SSG 模式对比;构建细节见构建工具

下一步

  • [ ] 镜像官方文档分篇(类似 Vue 3 栏目),把 Islands、Content Layer、渲染模式展开为独立子页
  • [ ] 搭一个"文档站 + MDX + View Transitions + Server Islands 评论区"的完整示例并跑构建
  • [ ] 对照 Next.js App Router 写"内容优先框架 vs 全栈框架"的选型对比

写作规范请参阅领域概览

基于 VitePress 构建 · 内容以知识共享方式沉淀