这个站是怎么搭起来的
一直想有个地方放东西。不要社交平台那种被排版好的格子,也不要一个需要维护数据库、 半夜会挂掉的博客系统。结论很简单:全部是静态文件,构建在本地做,服务器只负责发文件。
为什么是 Astro
选它的理由其实很朴素:
- 它内部就是 Vite。我另外两个外部实验(React + TS + Vite)用同一套工具链, 心智负担是零。
- 文章就是 Markdown 文件。写新文章 = 新建一个
.md,不需要登录后台。 - 默认产出的页面里没有 JavaScript。唯一的例外是
BaseLayout里的<ClientRouter />: 它做视图过渡,背景那层 canvas 也挂在astro:page-load上重建,所以站内换页不整页重载、 脚本不必重跑。我留着这点 JS,是因为站里那几个实验本身就是重交互的单页应用, 站内页面之间再来一次整页刷新并不划算。
博客内容的 schema 定义在 src/content.config.ts 里,frontmatter 写错了会在
构建时报错,而不是等到页面上出现一片空白才发现。这一点比什么都重要。
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
站点结构
整站只有一个构建产物目录,五个部分互不干扰:
| 路径 | 内容 | 来源 |
|---|---|---|
/ |
首页与博客 | 本站,Astro 构建 |
/synth/ |
6 算子 FM 合成器 | 实验仓库,单文件产物 |
/magi/ |
MAGI 决策系统 | 实验仓库,多文件构建产物 |
/pattern/ |
细胞自动机观测终端 | 本站仓库,单文件产物 |
/morph/ |
波貌合成器 | 本站仓库,单文件产物 |
这些实验自身的 JS、CSS 和图片用的都是相对路径(base: './'),所以 app 自己的产物丢到
任何子目录下都能跑,不需要任何 rewrite 规则——这一点在部署时省掉了大量麻烦。
但站点级的资源走的是根绝对路径:字体挂在 /fonts/ 下,文件名里带一段内容哈希;
/pattern/ 和 /morph/ 引的都是那几款自托管字体,/synth/ 和 /magi/ 引的是站点统一的
favicon 资源,同样带哈希。
所以 app 可以单独搬到别的子目录,但把它整个拎到站外,字体就会丢。
同步产物由 scripts/sync-apps.mjs 负责,把实验仓库里 /synth/ 与 /magi/ 的构建输出
复制进 public/;/pattern/ 和 /morph/ 是本仓自有的,不走这一步:
npm run sync:apps # 只同步
npm run build:full # 同步 + 构建整站
产物瘦身之后有几处变化:上游那批 design_test/ 测试页已经在同步时清掉,线上访问会返回 404;
MAGI 的字体从思源宋体换成了 IBM Plex Sans Condensed 与 Oswald 变量字体,
那条指向不存在文件的根绝对路径引用也一并清掉了;本站自己的字体也不再走 Google Fonts,
改成同源自托管的 /fonts/*.woff2。整站产物因此从 45 MB 降到了 3.6 MB 上下。
部署
服务器是一台内存很小的机器。不在上面构建——上面连 Node 都没装。 只装一个 nginx,剩下的事情就是发文件。
整套流程已经收进 scripts/deploy.sh,配置全走环境变量,默认值对应本站当前那台服务器:
bash scripts/deploy.sh # 发布
bash scripts/deploy.sh --dry-run # 只看要传什么,不实际发布
脚本里有四件事值得留意:
- 前置检查:
dist/存在、dist/index.html在、私钥在。少一样就在本地停下, 不浪费一次连接。 - 连通性探测:先用一个不产出输出的
ssh … true探一下,通不通在这一步就暴露, 而不是传到一半才断。 - 解包后先校验:新版本先解到旁边的临时目录,确认
index.html真的在里面, 才把线上目录换过去;没通过就放弃,线上那份原样不动。 - 发完自检:换好之后逐个 curl 站点的主要路径,把状态码打出来。
发布完还会保留一份带时间戳的备份,真出了问题可以回滚到上一版。
nginx 那侧就三件事:gzip on、带哈希的资源长缓存、.html 不缓存。
因为产物文件名里带哈希,缓存规则可以写得很激进而不用担心用户拿到旧版本。
还欠着的地方
- 还没有域名,所以暂时是
http://<IP>裸跑,也就没有 HTTPS。 - 背景那层 canvas 刻度网格很轻,但没有做滚动视差——先保证不卡。
先这样。能跑起来比什么都强。