这个博客是怎么搭起来的:Astro + Fuwari 的四个真实坑
这个博客是我自己搭的,从选型到踩坑前后折腾了不少时间。常有朋友问用的什么方案,这篇就一次说清楚:技术栈是什么、为什么这么选、以及几个真把人卡住的坑。用的技术都挺主流,坑却都很具体,如果你也打算用 Astro 搭个人站,这篇大概能帮你省掉一两个下午。
技术选型
| 部分 | 用了什么 |
|---|---|
| 站点生成 | Astro 5(纯静态输出) |
| 交互组件 | Svelte 5 |
| 样式 | Tailwind 3 + Stylus |
| 站内跳转 | @swup/astro(无刷新导航) |
| 全文搜索 | Pagefind(构建后生成索引) |
| 部署 | GitHub Actions → rsync → 自己的 VPS + nginx |
选静态生成的原因很简单:博客内容几乎不变,不需要服务端。构建出一堆 HTML 直接丢给 nginx,没有数据库、没有运行时,安全面和维护成本都降到最低。代价是评论、访问统计这些必须依赖第三方服务,这一点后面会提到。
主题基于开源的 Fuwari 二次开发,但下面这些坑大多不是主题特有的,而是 Astro 这套体系本身的特性。
坑一:文章标题就是 URL
本站的文章地址长这样:
https://www.daitcc.top/posts/4584a3ea/后面那 8 位十六进制不是随机数,而是 sha1(文章标题) 的前 8 位。
后果很直接:改一次标题,这篇文章的 URL 就变了。旧链接立即 404,搜索引擎已经收录的地址也会失效,如果之前分享过给别人,那些链接全部作废。
所以要改标题就得自己做重定向。我现在的做法是标题定下来就不再动,措辞不满意宁可改正文。
顺带一提,文章的文件名和目录名不参与 URL 计算,那些可以随便改。
坑二:改了插件,构建成功但产物没变
Astro 5 把 Markdown 的渲染结果缓存在 node_modules/.astro/ 下。问题在于,缓存失效的触发条件里不包含插件源码 —— 只有内容文件或 astro.config.mjs 变化才会让它失效。
于是会出现一种非常迷惑的现象:
- 改了
src/plugins/下的插件; astro check通过,pnpm build显示构建成功;- 但产出的 HTML 和上一次字节级完全相同。
因为构建根本没重新渲染 Markdown,直接用了缓存。
解决办法是手动清掉缓存再构建:
rm -rf node_modules/.astro && pnpm build判断自己是不是踩了这个坑,有个很省事的信号:正常的全新构建会打印一批内容同步日志,如果日志里那些行不见了,就说明走的是缓存。
坑三:无刷新跳转下,脚本必须幂等
Swup 接管站内跳转后,页面不会整页刷新,只替换指定的容器。这带来一个容易被忽略的结果:
被替换容器里的内联 <script> 每次切页都会重新执行一遍。
如果脚本里写的是「给按钮绑一个点击事件」,那么访问得越多,绑定的监听器就越多,最后点一下按钮触发十几次。注册 IntersectionObserver、ResizeObserver 这类对象时同理,会不断堆积。
所以这类脚本一律要写成幂等的初始化函数:
- 用
dataset打个标记,已经初始化过就直接返回; - 注册新 observer 之前,先把上一个
disconnect()掉。
另外,新页面里的 DOM 是重新插入的,事件需要重新绑定,通常得在 Swup 的 content:replace 钩子里再初始化一次。
坑四:正文标题层级会被构建期归一化
为了让每篇文章有且只有一个 h1(也就是页面标题),我在构建流程里加了一个 remark 插件,会在渲染时做两件事:
- 如果正文的第一个标题是
h1,或者它的文字和 frontmatter 里的title完全一样,就删掉它; - 把剩下的标题整体平移,让最浅的一级落在
h2。
结果是:无论我在 Markdown 里从 ## 还是 ### 开始写,渲染出来都是从 h2 开始,页面标题始终是唯一的 h1。写的时候不用纠结层级从哪起,但也意味着正文里不能用 h1 当章节 —— 那样会被当成重复标题删掉。
图片路径的三种写法
这一点也值得单独说,因为三种写法含义完全不同,写错了不会报错,只是图出不来:
| 写法 | 相对谁 | 会不会被优化 |
|---|---|---|
assets/images/x.png | src/ 目录 | 会,走 astro:assets |
/images/x.png | public/ 目录 | 不会,原样拷贝 |
./x.png | 当前 Markdown 文件 | 会,走 astro:assets |
放在 public/ 下的文件会被直接复制到产物里,适合那些不希望被处理的资源;放在 src/ 下的图片则会经过 Astro 的图片流水线,自动生成多尺寸、转成现代格式。
部署
推送到 master 分支就会触发 GitHub Actions:构建 → 把产物 rsync 到 VPS。因为服务器上只跑 nginx 提供静态文件,所以整个流程就是一次文件同步,几秒钟结束。
唯一需要额外配置的是 404 页面 —— Astro 会产出一个 404.html,但还得在 nginx 里写一行 error_page 404 /404.html;,否则访问不存在的路径看到的仍是 nginx 的默认页。
以上就是这个站点的完整搭法。技术栈本身没什么新鲜的,真正花时间的是上面这四个坑 —— 它们共同的特点是:不报错、构建也成功,只是结果和你预期的不一样。这类问题最难查,所以专门记下来。
如果这篇对你有帮助,或者你也踩过类似的坑,欢迎在下面留言交流。