这是 wzk.icu 重构后的第一篇文章,记录一下这次重构的过程和选型理由。
为什么要重构
原来的站点是一个手写的 HTML + CSS 页面,有点好看,但维护起来很痛苦:
- 想写博客?没有,只能在 CSDN / 掘金发,流量都贡献给了别人的平台
- 想加新内容?直接改 HTML,每次都得小心不破坏样式
- SEO?几乎没有,meta 标签就是随便写的
- 版本管理?代码在 Git 里,但内容和代码完全混在一起
目标很明确:能方便地用 Markdown 写文章,有完整的 SEO 基础设施,风格统一,可以长期维护。
为什么选 Astro
考虑过几个方案:
| 方案 | 优点 | 为什么没选 |
|---|---|---|
| Next.js | 生态大、功能强 | 太重,个人站不需要 SSR,Vercel 免费额度有限 |
| Hugo | 极快、纯静态 | 模板语言学习成本高,不如 JSX/组件化直观 |
| VitePress | 专注文档 | 不适合做门户页,定制性差 |
| Astro | SSG、组件化、内容集合 | ✅ 正好 |
Astro 的核心优势:
- Island Architecture:默认零 JS,只有需要交互的组件才加载 JS
- Content Collections:内置的 Markdown 内容管理,有 Schema 校验
- 框架无关:可以用 React/Vue/Svelte 写组件,但这个站用纯
.astro就够了
遇到的坑
Bun 替代 npm
Node 18 版本太低,npm create astro 直接报错。换用 Bun:
bun create astro@latest
Bun 比 npm 快很多,包安装速度体感有 3-5 倍的差距。但要注意所有命令都得用 bun run,不能混用 npm。
Astro 6 的 Content Collections 新 API
网上大多数教程还是 Astro 4/5 的写法,Astro 6 有几处 breaking change:
配置文件位置变了:
旧:src/content/config.ts
新:src/content.config.ts(移到根目录)
Loader 写法变了:
// 旧写法
const blog = defineCollection({ type: 'content', schema: z.object({...}) });
// 新写法(Astro 6)
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({...}),
});
文章渲染变了:
<!-- 旧:需要异步调用 render() -->
const { Content } = await post.render();
<Content />
<!-- 新:直接用 rendered.html -->
<div set:html={post.rendered.html} />
路由参数变了:
// 旧:post.slug
// 新:post.id
params: { slug: post.id }
这几个坑在迁移时全踩了,花了不少时间 debug。
Fixed Header 遮挡内容
Header 设置了 position: fixed,但页面 main 没有对应的 padding-top,内容被遮住一截。
解法:每个页面的 main 加 padding-top: 56px(与 Header 高度一致)。
SEO 基础设施
这次重构把 SEO 当成一等需求,而不是事后补上:
- SeoHead 组件:统一管理
<title>、meta description、OG、Twitter Card、canonical - JSON-LD:Person + WebSite + Article 结构化数据,让搜索引擎和 AI 正确识别内容
- Sitemap:
@astrojs/sitemap自动生成 - robots.txt:动态生成,指向 sitemap
- RSS Feed:博客 + 项目合并订阅
- llms.txt:专门为 AI 搜索引擎写的站点说明(类似 robots.txt,但面向 LLM)
结果
构建后是纯静态 HTML,托管在 Vercel 上,加载速度极快,维护成本低。写文章只需要在 src/content/blog/ 下新建一个 Markdown 文件,推送到 GitHub,Vercel 自动构建部署。
这才是个人站应该有的形态。