3182 字
16 分钟
这个博客是怎么搭起来的:从 Fuwari 到一套自己的 Astro 工作流

这个博客最开始并没有什么宏大的目标。

我只是想要一个自己真正愿意长期写下去的地方:能记技术踩坑,也能写一些还没有完全想明白的东西;最好不依赖某个平台,不被特定的编辑器和发布流程绑住,哪天想搬走时,所有文章仍然只是普通的 Markdown 文件。

所以现在看到的这个站,虽然外观仍然能看出 Fuwari 的影子,但底下的工作流已经改了不少。

这篇就当作一次阶段性记录,简单拆一下它现在是怎么工作的,以及这段时间到底改了些什么。


一、先看整体架构#

目前整个博客大致可以画成这样:

┌─────────────────────┐
│ GitHub Repo │
│ Markdown + Code │
└──────────┬──────────┘
│ push
▼
┌─────────────────────┐
│ GitHub Actions │
│ install / check │
│ build / Pagefind │
└──────────┬──────────┘
│ dist
▼
┌─────────────────────┐
│ Wrangler │
│ Direct Upload │
└──────────┬──────────┘
▼
┌─────────────────────┐
│ Cloudflare Pages │
│ CDN / HTTPS / 托管 │
└──────────┬──────────┘
▼
blog.ly2u.com
后台编辑:
浏览器 → Decap CMS → GitHub OAuth → GitHub Repo
图片:
浏览器 → 自建图床 → 边缘加速 → Markdown 外链

核心思路其实很简单:

GitHub 保存源文件,GitHub Actions 负责构建,Cloudflare 只负责托管和分发。

这和最开始直接把 GitHub 仓库接给 Cloudflare Pages 已经不太一样了。

以前每次提交代码,Cloudflare 会自己拉源码、安装依赖、重新构建;后来 GitHub Actions 本身也在做一遍 pnpm build 检查,相当于同一份代码被构建两次。

现在干脆把职责拆开:

  1. GitHub Actions 安装依赖;
  2. 运行 Astro check;
  3. 正式构建静态站;
  4. 生成 Pagefind 索引;
  5. 得到最终 dist/;
  6. Wrangler 直接把成品上传到 Cloudflare Pages。

Cloudflare 不再参与源码构建,只做静态文件托管、HTTPS 和 CDN。

对一个纯静态博客来说,这套关系反而更清楚。


二、底层还是 Astro,但尽量少让前端“动起来”#

博客目前基于 Astro,最初从 Fuwari 主题改出来。

我喜欢 Astro 的一个原因,是它很适合这种以阅读为主的网站:大部分内容在构建时直接生成 HTML,不需要为了显示一篇文章先加载一整个前端应用。

现阶段主要用到的东西包括:

  • Astro:静态站主体;
  • Tailwind CSS:页面样式;
  • Svelte:少数确实需要交互的设置组件;
  • Pagefind:静态全文搜索;
  • Swup:站内页面切换;
  • Expressive Code:桌面端代码高亮;
  • PhotoSwipe:图片查看;
  • OverlayScrollbars:桌面滚动体验;
  • Decap CMS:网页端文章编辑后台。

不过后来的优化方向,反而一直是在做减法。

比如搜索并不会一打开网页就加载 Pagefind,而是第一次点击搜索时才加载;PhotoSwipe 也是用户真正打开图片时才引入;OverlayScrollbars 只在桌面端、精细指针设备上初始化。

右侧文章目录也是一样。以前只是用 CSS 在移动端把它隐藏,JavaScript 仍然会扫描标题、创建 Observer、计算位置。现在小屏幕下干脆连目录逻辑都不初始化。

对博客来说,很多“功能”其实并不需要一直在线。


三、真正折腾得最久的,是 iPhone 上的长文章#

这个站目前改得最深的一块,其实不是外观,而是移动端代码阅读。

之前写图床那篇文章时,里面有很多代码,还有一个接近 200 行的 Go 程序。Android 和桌面都正常,但在 iPhone Safari 上快速滑动长文章时,正文会突然出现大面积空白,停下来之后才重新补绘。

最开始怀疑过很多东西:

  • 右侧 TOC;
  • content-visibility;
  • 页面动画;
  • backdrop-filter;
  • 多层 overflow:hidden;
  • 代码块自己的滚动条。

最后做了一轮比较彻底的 A/B,才发现真正影响最大的,是长页面中常驻的 Expressive Code 复杂 DOM。

桌面端完整的代码块很好看,但一段几百行的高亮代码会生成大量 token <span>。多个代码块叠加后,iOS WebKit 在快速滚动时很容易出现绘制跟不上的情况。

最后没有继续硬调 CSS,而是换了一个思路:

正文负责阅读,代码按需查看。

现在移动端的规则是:

  • 很短的代码转成轻量代码框;
  • 稍长的代码在正文里变成“代码卡片”;
  • 点击卡片后,再打开独立的全屏代码阅读器;
  • 完整的代码 DOM 只在需要时出现,关闭阅读器后就销毁;
  • 桌面端仍然保留完整 Expressive Code。

这其实比原来直接把 200 行代码铺在手机正文里更合理。

最初只是为了修一个 Safari 性能问题,最后反而变成了一个我更喜欢的移动端阅读方式。


四、Swup 也踩过一个很隐蔽的坑#

另一个比较典型的问题,是桌面端点击页面时会“闪两下”。

一开始以为是两套过渡动画叠加,于是把内容入场动画、Swup fade 动画都关掉过,但问题仍然存在。更奇怪的是,录屏里连地址栏都会出现短暂的:

/目标页/ → / → /目标页/

这就说明它不是单纯的视觉闪烁,而是真的发生了二次导航。

最后查到 Swup 配置要求每次同时替换:

main
#toc

但旧布局里 #toc 是条件渲染的:文章有目录时才存在,首页、归档、传送门等页面可能根本没有这个节点。

对于 Swup 来说,不同页面的容器结构并不稳定,于是切页时就可能出现异常回退。

最终的解决办法很简单:

所有页面始终保留 #toc 容器,没有目录时只让它为空,而不是让节点本身消失。

修完以后,双跳转和闪屏都消失了。

这个问题也挺有代表性:有时候看起来像“动画问题”,真正的根因却是页面结构契约被破坏了。


五、后台并不是另一个数据库,文章仍然只是 Markdown#

我不太想为了一个个人博客再维护一套数据库和复杂后端,所以后台编辑继续使用 Decap CMS。

它本质上只是一个 Git 的图形化编辑界面。

在浏览器中打开后台后,通过 GitHub OAuth 完成身份验证;文章编辑、创建、保存,最后仍然是对仓库里的 Markdown 文件提交 commit。

也就是说,无论文章是:

  • 在本地编辑器里写;
  • 在 GitHub 里直接改;
  • 在 Decap 后台改;
  • 或者由自动化工具修改;

最后落到仓库里的东西都是同一种格式。

这一点对我来说很重要,因为 Markdown 才是最终的数据源,后台只是一个入口。

目前 Decap 还做了几处自己的定制:

  • 后台界面做了简单的中文化和样式整理;
  • 粘贴图片时自动压缩为 WebP;
  • 图片不进入 Git 仓库,而是直接上传到自建图床;
  • 上传成功后自动插入 Markdown 图片链接;
  • 可以从 GitHub 重新读取文章原始 Markdown;
  • 保留一个快速进入外部 Markdown 排版器的入口。

这样仓库不会因为图片越来越多而迅速膨胀。


六、图片和文章分开存#

图片目前走的是自建图床,而不是把所有原图都塞进博客仓库。

编辑后台里粘贴一张图片时,大致会经历:

剪贴板图片
↓
浏览器 Canvas 压缩
↓
WebP
↓
自建上传接口
↓
图床存储
↓
边缘加速
↓
返回 https 图片地址
↓
插入 Markdown

浏览器端会限制最大尺寸,并在上传前进行一次压缩。

这样做的好处很实际:Git 仓库里主要还是文字和代码,构建速度、clone 体积和历史记录都不会被大量二进制图片拖累。

头像后来也从仓库里的 2MB 多 PNG 改成了图床上的 WebP。


七、发布日期和最后修改日期分开了#

以前文章只有 published。

但博客文章发出去之后,后面很可能还会继续修错字、补步骤、改代码。如果修改过很多次,只有一个发布日期并不能完整说明文章状态。

现在的做法是:

  • published 继续写在 frontmatter 中,代表第一次发布;
  • 构建时通过 Git 历史读取这篇 Markdown 最近一次 commit 时间;
  • 只有文件至少发生过第二次提交时,页面才显示“最后修改日期”;
  • JSON-LD 中同时写入 dateModified。

这样不需要每次手工维护 updated 字段。

无论通过哪种方式改文章,只要最终进入 Git 历史,更新时间就会自动变化。

文章标题旁边现在也放了一个很轻的编辑图标。对普通访问者来说它只是一个入口;真正点击后仍然需要经过 GitHub OAuth 和仓库权限验证。

对自己来说,就可以从“正在看的文章”直接进入“这篇文章的后台编辑页面”。


八、从 Fuwari 到现在,主要改了什么#

如果只看目录结构,这个站仍然能找到不少 Fuwari 的东西;但实际使用体验已经改了很多。

目前比较明显的变化包括:

内容层#

  • 删除主题自带的演示文章和示例内容;
  • 重做“关于我”和“传送门”;
  • 增加友链区域;
  • 增加更符合自己使用习惯的站点描述和导航;
  • 增加自动最后修改时间;
  • 增加文章快速编辑入口。

性能层#

  • Pagefind 改为第一次搜索时再加载;
  • PhotoSwipe 改为按需加载;
  • OverlayScrollbars 只在桌面初始化;
  • 移动端不初始化右侧 TOC;
  • 滚动和 resize 监听做节流;
  • 移动端关闭高成本导航栏模糊;
  • 减少不必要的 GPU layer 和入场动画;
  • 优化移动端网格,不再保留隐藏的桌面列;
  • 大头像改走体积更小的 WebP 外链。

移动端代码#

  • 移除正文中长期存在的重型 Expressive Code DOM;
  • 短代码使用轻量代码框;
  • 长代码使用代码卡片;
  • 点击后进入独立全屏阅读器;
  • 代码阅读器保留行号、复制、横向滚动和语法高亮。

构建与发布#

最早:

GitHub → Cloudflare 拉源码 → Cloudflare 构建 → Pages

现在:

GitHub
↓
GitHub Actions
↓
pnpm install
pnpm check
pnpm build
↓
dist
↓
Wrangler
↓
Cloudflare Pages

这也是我目前比较满意的一次调整:构建环境被固定在 GitHub Actions,Cloudflare 只接收最终产物。


九、为什么没有继续往“更复杂”做#

如果愿意,这个博客当然还可以继续堆很多东西:数据库、评论、账号体系、动态 API、在线草稿、实时预览……

但目前我更希望它保持一种比较克制的状态。

一篇文章最核心的东西仍然是一个 Markdown 文件;网站本身可以整个重新构建;图床可以单独迁移;Cloudflare 只是托管层;后台坏了也不影响文章源文件。

换句话说,各部分之间有联系,但尽量不互相绑死。

这也是我现在越来越喜欢的一种搭建方式:

能静态就静态,能在构建阶段解决的就不要留到浏览器运行时;能保持普通文件格式的,就不要急着塞进数据库。


十、接下来还会继续改#

现在这个站当然还没有“做完”。

实际上个人博客大概也不存在真正做完的时候。

后面可能还会继续调整:

  • 后台编辑体验;
  • 文章版本与修改记录;
  • 搜索结果展示;
  • 移动端代码阅读器细节;
  • RSS、SEO 和结构化数据;
  • 友链与独立博客之间的连接方式;
  • 一些真正对写作有帮助、而不是纯粹为了“功能更多”的小工具。

不过至少到现在,底层的结构已经比较清楚了。

它不再只是“下载一个主题然后部署”的博客,而是慢慢变成了一套符合自己习惯的写作和发布工具。

某种意义上,这也正是我想做博客的原因:

不是先把一切设计完整再开始记录,而是在持续记录的过程中,让工具和自己一起慢慢成形。

这个博客是怎么搭起来的:从 Fuwari 到一套自己的 Astro 工作流
https://rqly.com/blog/posts/2026-09-13-这个博客是怎么搭起来的从fuwari到一套自己的astro工作流/
作者
L·Y
发布于
2026-09-13
许可协议
CC BY-NC-SA 4.0
1
一、先看整体架构
2
二、底层还是 Astro,但尽量少让前端“动起来”
3
三、真正折腾得最久的,是 iPhone 上的长文章
4
四、Swup 也踩过一个很隐蔽的坑
5
五、后台并不是另一个数据库,文章仍然只是 Markdown
6
六、图片和文章分开存
7
七、发布日期和最后修改日期分开了
8
八、从 Fuwari 到现在,主要改了什么
内容层
性能层
移动端代码
构建与发布
9
九、为什么没有继续往“更复杂”做
10
十、接下来还会继续改