用 Astro 从零搭建个人站点
记录岛屿架构、内容集合与部署上线的完整流程,以及搭建过程中关于性能与取舍的一些思考。
个人博客这个「老掉牙」的需求,这两年因为静态站点生成器的成熟又重新变得有趣起来。在试过 Hugo、Next.js 之后,我最终选择了 Astro,并把从零搭建到上线的完整流程记录在这里。这篇文章不打算复述官方文档,而是聚焦几个我在实际搭建中反复权衡过的决策点。
为什么是 Astro
先说我放弃其他方案的原因:
- Hugo:快,模板语法
Go template学习曲线陡峭,且内容组件化能力弱。 - Next.js:对于纯内容站,把 React 作为默认渲染运行时是过度设计——既增加了首屏 JS,也让「内容即数据」的模型变得繁琐。
Astro 的核心思路是「默认零 JS」:页面在构建期渲染成纯 HTML,只有真正需要交互的组件才按需加载脚本。这套被称为 岛屿架构(Islands Architecture) 的方案,对一个 90% 内容 + 10% 交互的个人站点来说几乎是量身定做。
另外它的一个隐藏优势是内容集合(Content Collections):给 Markdown 前置元数据加上类型校验,写错字段在构建期就直接报错,而不是上线后才发现。
初始化与目录结构
npm create astro@latest -- --template minimal
生成的项目结构:
.
├── src/
│ ├── components/ # 可复用组件(Header、Footer…)
│ ├── layouts/ # 页面布局
│ ├── content/ # 内容集合(Markdown 文章)
│ ├── pages/ # 路由(文件系统路由)
│ └── content.config.ts
├── astro.config.mjs
└── package.json
src/pages/ 下的文件路径即路由:src/pages/blog/index.astro 对应 /blog/,而动态路由用方括号命名。
内容集合:给 Markdown 加类型
内容集合是 Astro 5 之后用 content layer 实现的能力。先在 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(),
summary: z.string(),
tags: z.array(z.string()),
}),
});
export const collections = { blog };
然后在文章里写前置元数据:
---
title: '用 Astro 从零搭建个人站点'
date: '2026-07-28'
summary: '…'
tags: ['Astro', '建站']
---
此后 getCollection('blog') 返回的数据都经过 Zod 校验——日期写成了非法格式、忘了写 summary,构建直接失败,这比任何 lint 规则都让人安心。
岛屿架构:哪里需要 JS 才加 JS
默认情况下 Astro 组件在服务端渲染成静态 HTML,不向浏览器发送任何 JavaScript。只有当你需要交互(比如一个计数器、一个搜索框)时才显式声明:
---
import Counter from '../components/Counter.tsx';
---
<!-- 只有这个组件会被水合(hydrate) -->
<Counter client:load />
client:* 指令控制水合的时机:
| 指令 | 触发时机 |
|---|---|
client:load |
页面加载后立即水合 |
client:idle |
浏览器空闲时水合 |
client:visible |
组件滚动进入视口时水合 |
client:only |
仅在客户端渲染(适合纯前端组件) |
实际效果:一个博客首页如果只有一个搜索框需要交互,那浏览器加载的 JS 可能就是这一个小组件——其余全是静态 HTML。
性能与取舍的几点思考
搭建过程中我意识到,静态站点生成器的「快」其实分两个层面:
- 构建快:内容规模固定时,构建时间几乎恒定。
- 加载快:首屏纯 HTML + 零 JS,配合
buildOutput: 'static'(默认),可以直接丢到任何静态托管。
但在一个点上我做了取舍:为了阅读体验,我把渲染 Markdown 的 Shiki 代码高亮体积控制在了主题内的行内样式,而不是引入一整套高亮 JS。代价是每个代码块多了一点 HTML 体积,换来的是零运行时开销。个人站点这个体量,这种取舍是划算的。
部署上线
Astro 的产物是纯静态文件,部署选择非常多。我的流程是:
npm run build # 产物输出到 dist/
然后推到 GitHub 仓库,用 Vercel / Netlify / Cloudflare Pages 任一平台自动部署,或者在服务器上用 Nginx 直接托管 dist/。如果将来想接入后端接口,也可以把某个路由标记为 server 输出,按需混合。
小结
Astro 给我的感受是「克制」:它不替你决定运行时,而是把选择权留给你——默认无 JS,需要时再水合;内容即数据,类型即文档。对个人博客这种「内容为主」的场景,它提供了几乎最优的默认值,而需要取舍的地方,你也能清楚地看到成本在哪里。
下一篇我会写容器查询的实战笔记,敬请期待。