换电脑、迁移 Astro、修 bug

第一篇文章发出去不到 24 小时,开发的设备因为一些原因用不了了。好在代码已经推到 GitHub 上,在新电脑上 clone 下来就能继续。

但事情没有想象中那么顺利。

环境

新电脑上什么都没有。没有 Python,没有 Node.js,没有字体,没有编辑器。从头来。

博客已经在前一台设备上迁到了 Astro,所以这次只需要装 Node.js 就够了。但 Windows 上装 Node.js 也出了一点状况——winget 不可用,直接下载安装包需要管理员权限兜兜转转最后用了一个之前解压到一半的 v22.12.0。把路径写到系统环境变量里,重启终端,终于认了。

node --version  # v22.12.0
npm --version   # 10.9.0

npm install 一把过。

架构

第一篇文章里提到过,最初的博客是纯 Python 方案——每个 Markdown 文件手动转成一段 HTML,然后用字符串模板拼进 base.html 里。简单高效,但谈不上什么设计感,MacOS 风格的模糊导航栏加上麦当劳配色(红色导航栏配白色背景),能用就行。

这次迁移到 Astro,不只是换了个构建工具。借着重新拆解组件的机会,也重新设计了整个视觉。

配色

键盘圈有一个经典的复古配色叫 GMK 9009,cream + cement + orange,暖灰调里带一点橙色点缀,像是从八十年代的工业设计手册里撕下来的。我之前折腾机械键盘的时候就对这个配色印象很深。

把它搬到了博客上:

:root {
  --cream:   #F5F3EE;  /* 页面底色,乳白偏灰 */
  --cement:  #C4C9C9;  /* 边框、分割线、侧边栏标题 */
  --orange:  #F46822;  /* 链接、高亮、hover */
  /* ... */
}

不是纯白背景戳眼睛,不是纯黑看久了压抑。比第一版的麦当劳配色安静很多,也更耐看。

字体选了 JetBrains Mono——程序员最喜欢的等宽字体之一,以前用 Monaco,这次换过来。字型大了一圈,行宽也放宽了,每行大概 80 个字符左右。

组件化

原来的 base.html 是一整坨 200 行的大模板,Header、侧边栏、搜索框、Footer 全部揉在一起,改任何东西都得扫一遍全文。

拆成 Astro 组件之后:

  • Base.astro:页面骨架,包含 Header、侧边栏、Footer,通过 <slot /> 插入页面内容
  • index.astro:首页,遍历文章列表生成摘要
  • [slug].astro:文章详情页,Markdown 渲染 + KaTeX 数学公式

侧边栏是整站变化最大的地方。之前只有一个链接列表,现在加上了 About 简介、全部文章归档、以及 Elsewhere 外部链接。每次都手动从文章列表里更新最近文章,虽然麻烦,但相比之前的那种”孤零零的页面”,已经有了点”博客”的样子。

三个 bug

npm run build,报错。

图片路径

第一篇文章里有一张天空的照片。在旧的 Python 方案里,图片放在项目根目录的 images/ 下,文章里用 images\xxx.jpg 引用。Markdown 解析器不关心斜杠方向,直接拼路径就行。

Astro 不一样。它用 Vite 处理资源,图片需要用正斜杠,而且路径是相对于页面根的。更关键的是——图片得放在 public/ 目录下。这是 Astro 的约定:public/ 下的文件原样输出,不经过 Vite 的处理管线。

修改:

- ![sky](images\img_v3_02146_xxx.jpg)
+ ![sky](/images/img_v3_02146_xxx.jpg)

顺便把图片挪到 public/images/

搜索链接

搜索功能是纯前端方案:构建时生成 search.json,一段 JS 做即时检索。旧的代码里搜索结果链接写死了 .html 后缀:

'<a href="' + p.slug + '.html" class="search-result-item">'

但 Astro 默认生成的是 Clean URL(/posts/slug/ 不带 .html),所以搜索结果点进去全是 404。

改成:

'<a href="/posts/' + p.slug + '/" class="search-result-item">'

CSS 丢了

astro build 不报错,但打开页面是一个毛坯房——所有的样式都没了。检查一下生成的 HTML,<head> 里只有一个 Google Fonts 的 <link>,没有任何 CSS。

原因在 Base.astro 里:

import '../styles/global.css';

这段代码看起来没问题——在 Astro 的文档里这也是推荐写法。但不知为什么,Vite 在处理的时候没有把它打包进去。dist/_astro/ 下只有 KaTeX 的字体文件,没有任何 CSS 文件。

排查了一圈,最简单的解决方案是把 CSS 放到 public/ 下让它原样输出,然后在 <head> 里用传统方式 link:

- import '../styles/global.css';
+ <!-- 移到 public/styles/ -->

  <link rel="stylesheet" href="/styles/global.css">

CSS 文件本身不用改,一个字都不动。

清理

旧的 Python 时代遗留下一些文件——posts/ 目录(文章已经移到 src/content/posts/ 里了)、多余的 images/ 目录。这些都删干净了。README 也更新成 Astro 的说明。

迁移前后对比

Python 方案Astro
本地预览python dev.pynpm run dev
构建python build.pynpm run build
热更新文件变化触发完整 rebuildHMR,只刷新变化的部分
文章存放posts/*.mdsrc/content/posts/*.md
图片images/public/images/
布局Python 模板字符串Astro 组件
数学公式MathJax(按需加载)KaTeX(构建时渲染)
搜索纯前端 JSON + JS同上,基本没变
配色麦当劳红白GMK 9009 Cream + Cement + Orange
排版Monaco,窄行宽JetBrains Mono,放宽行宽

Astro 带来的最大改进是组件化。侧边栏、文章列表、页面头尾——每个都是独立的组件,改一个地方全站生效。之前改一下 CSS 要手动 rebuild 所有页面,现在保存就立即看到结果。

之后

这次迁移暴露了一个问题:import CSS 在 Astro 里时灵时不灵。可能跟具体版本有关,也可能是路径解析的问题。目前用 <link> 的 workaround 能用,但之后想搞清楚为什么——也许能变成一篇 debug 笔记。

Permalink
这个博客是怎么搭起来的

我一直想有一个自己的博客。不是那种用现成主题、五分钟搭好的博客,而是一个每一行 CSS 我都知道为什么这么写的博客。

起点

Simon Willison 的博客是我一直以来的参考。内容即界面,没有多余的东西——没有侧边栏小部件、没有社交分享按钮、没有评论区。打开页面,只有文章。

我想做类似的东西,但有几个自己的偏好:

  • 全站等宽字体。JetBrains Mono,我喜欢它干净利落的字形
  • 不依赖任何框架。GitHub Pages 原生支持 Jekyll,但在 Windows 上装 Ruby 很痛苦。与其和工具链搏斗,不如回到最原始的方式
  • Markdown 写作。不想手写 HTML,但也不想为了预览等 30 秒的构建

技术路线

最开始尝试了 Jekyll。本地跑不起来,每次改 CSS 都要靠想象,改完 push 上去等 GitHub Pages 构建才知道效果——这个反馈循环太慢了。

后来改成了纯 HTML。所有的 .md 文件用 Python 脚本转换成 HTML,本地 python dev.py 启动一个服务器,保存文件后浏览器自动刷新。零构建时间。

几个关键的库:

markdown          # Markdown -> HTML
Pygments          # 代码语法高亮
python-frontmatter # 解析文章元数据

加起来不到 100 行的 build.py,做了三件事:

  1. 读取 posts/*.md,解析 frontmatter(标题、日期、类型)
  2. 转换成 HTML,套上模板
  3. 生成首页文章列表和搜索索引

搜索用的是纯前端方案:构建时生成 search.json,页面上一段 50 行的 JS 做即时检索。不需要任何后端。

三种内容类型

我不总是写长文章。很多时候只是记录一下今天做了什么,或者分享一篇读到的好东西。所以设计了三种内容类型:

类型用途例子
Article正式长文就是这篇
Note每日小结今天修了三个 bug,学了两章 Rust
Link资源分享推荐文章 + 简短评论

首页会展示所有类型的全文,按时间倒序排列。长文章底部有一个 Permalink 链接。

设计

配色是在路过麦当劳的时候定下来的。麦当劳门店那种红、金、暖灰的组合意外地好看。

  • 主色 #DA291C(麦当劳红)——侧边栏、链接、按钮
  • 金色 #FFC72C——代码块左边框
  • 暖灰 #E5E1DC——页面背景
  • Header 用了 #2D2D2D 深色,和灰色背景拉开层次

整个过程是聊出来的——我描述想法,Claude 帮我写代码。CSS 调了五六版才到现在的状态,中间踩了不少坑:

  • 全站等宽字体导致中文巨丑,最后加了 HarmonyOS Sans 作为中文字体回退
  • 自动刷新一开始用时间戳比对,结果是死循环——页面永远在刷新。改成版本号机制才修好
  • 首页展示全文后长文章太占地方,试了折叠展开的方案,JS 莫名其妙不生效,最后干脆去掉,用侧边栏的 All Posts 列表来导航

数学公式

文章里可以写 LaTeX 数学公式,用 MathJax 渲染。行内公式比如 E=mc2E = mc^2 可以直接写在段落里,块级公式也支持:

ex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}

只在包含公式的页面才加载 MathJax,不浪费带宽。

部署

整个博客是一堆静态 HTML 文件,托管在 GitHub Pages 上。更新流程:

python new.py              # 新建文章
# 编辑 posts/xxx.md
python build.py            # 构建
git add . && git commit -m "新文章"
git push                   # 上线

从写完到上线不超过一分钟。

更新:迁移到 Astro(2026-08-02)

纯 Python 的方案用了一阵子之后,还是发现了一些痛点:

  • 组件复用:侧边栏、Header 等在每篇文章的 HTML 里重复,稍微改一下设计就要 rebuild 所有页面
  • 构建速度:虽然每篇单独构建很快,但文章多起来之后全量构建变得越来越慢
  • 生态:想要 RSS、sitemap 等功能,自己写太累,用别人的又没有合适的工具链

于是把博客整个迁移到了 Astro。迁移过程比想象中顺利,因为之前的 HTML 模板结构已经很清晰:

  1. 把 Python 模板转成 Astro 组件(Base.astro 做布局,index.astro 做首页)
  2. Markdown 文件加个 frontmatter 头,放进 src/content/posts/
  3. 搜索和数学公式保留原来的方案(JSON + KaTeX)
  4. CSS 几乎一行没改

现在的工作流:

npm run dev           # 本地预览,热更新
# 编辑 src/content/posts/xxx.md
npm run build         # 构建
git push              # 上线

Astro 默认输出零 JS,和之前纯静态 HTML 的性能一样好。但开发体验好了很多——组件化让修改布局变得简单,热更新让写 CSS 快了几个数量级。

之后

这个博客会持续演进。目前想加的东西:

  • RSS feed
  • 文章标签(也许)
  • 暗色模式(也许)

但大概率不会加评论系统。保持简单本身就是一种功能。

sky

Permalink