← 返回博客列表

用 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。

性能与取舍的几点思考

搭建过程中我意识到,静态站点生成器的「快」其实分两个层面:

  1. 构建快:内容规模固定时,构建时间几乎恒定。
  2. 加载快:首屏纯 HTML + 零 JS,配合 buildOutput: 'static'(默认),可以直接丢到任何静态托管。

但在一个点上我做了取舍:为了阅读体验,我把渲染 Markdown 的 Shiki 代码高亮体积控制在了主题内的行内样式,而不是引入一整套高亮 JS。代价是每个代码块多了一点 HTML 体积,换来的是零运行时开销。个人站点这个体量,这种取舍是划算的。

部署上线

Astro 的产物是纯静态文件,部署选择非常多。我的流程是:

npm run build   # 产物输出到 dist/

然后推到 GitHub 仓库,用 Vercel / Netlify / Cloudflare Pages 任一平台自动部署,或者在服务器上用 Nginx 直接托管 dist/。如果将来想接入后端接口,也可以把某个路由标记为 server 输出,按需混合。

小结

Astro 给我的感受是「克制」:它不替你决定运行时,而是把选择权留给你——默认无 JS,需要时再水合;内容即数据,类型即文档。对个人博客这种「内容为主」的场景,它提供了几乎最优的默认值,而需要取舍的地方,你也能清楚地看到成本在哪里。

下一篇我会写容器查询的实战笔记,敬请期待。