TERMINAL FIELD个人站
HELLO-WORLD1291 字约 3 分钟

这个站是怎么搭起来的

发布 2026-10-16建站astronginx

一直想有个地方放东西。不要社交平台那种被排版好的格子,也不要一个需要维护数据库、 半夜会挂掉的博客系统。结论很简单:全部是静态文件,构建在本地做,服务器只负责发文件。

为什么是 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 刻度网格很轻,但没有做滚动视差——先保证不卡。

先这样。能跑起来比什么都强。

BACK 回到文章列表