
前三篇做完了本地的一切。这篇把它推到线上。
先看一遍本地构建
pnpm build 不是只跑 astro build,它实际上串了一长串:
pnpm build# 1. 同步内容仓 改的配置在这里变成 src/ 下的产物# 2. 生成图标 扫 src/,把用到的图标打进本地集合# 3. 生成说说缩略图# 4. 子集化字体 只保留文章里真正用到的字形# 5. astro build 真正的构建# 6. pagefind 生成全文搜索索引# 7. 字体校验这个顺序不是随便排的。第 1 步没跑,后面读到的全是旧产物;第 2 步没跑,config/ 里新写的图标名不会进包(上一篇讲过那条坑)。
懂了这个顺序,你就能解释大部分“本地构建结果和预期不一样”的情况。
字体子集化那一步值得单独看一眼数字。我这站用的中文字体源文件 14.52 MB,子集化之后 360 KB。效果是长文章用的中文显示更好看,首屏还不至于被字体拖死。
三个部署方案
| 方案 | 省事程度 | 代价 |
|---|---|---|
| Vercel / Netlify | 最省事,仓库连上去就能自动构建 | 国内访问速度随缘,域名绑定要额外折腾 |
| GitHub Actions + 自建服务器 | 中等 | 要自己配服务器、Nginx、证书 |
| 服务器上直接跑构建 | 不推荐 | 见下 |
第三条单独说一下为什么不行。
pnpm build 里最重的是字体子集化、图片处理和全文索引。2 核 2G 的小机器上跑,很容易内存打满或者疯狂用 swap,node_modules 本身还要占一两个 G。放到 GitHub Actions 上跑,那边是十几 G 内存的机器,几分钟完事。
所以结论是:服务器只做静态托管,永远不在上面构建。
我选的是第二条路。想把域名和服务器都握在自己手里,顺便走一遍备案流程。
双仓怎么串起来
双仓带来的一个额外好处是,CI 的职责也分开了:
| 仓 | 触发条件 | 干什么 |
|---|---|---|
| 内容仓 | 推 content/ config/ data/ assets/ public/ | 发一个事件给代码仓 |
| 代码仓 | 收到事件 | 拉内容仓、构建、把产物同步到服务器 |
流程是这样的:你在内容仓写完文章推上去,内容仓的 workflow 通过一个令牌通知代码仓,代码仓拉取内容、构建,然后用 rsync 把 dist/ 送到服务器。
如果你不想让两边都配 workflow,用 Vercel 那种平台的话,只要它支持同时拉两个仓库就行,但自建这条路上,双仓 CI 是必须的。
有个细节容易漏:内容仓的触发分支要写对。内容仓的默认分支和代码仓的名字可能不一样,写错了现象是“文章推上去了、线上没动静”,而日志里连报错都没有,因为 workflow 压根没被触发。
服务器:RHEL 系的两道墙
我用的是阿里云 ECS 上的 Alibaba Cloud Linux,属于 RHEL 系。这点很关键,别照网上的 Ubuntu 教程做:
| Ubuntu | RHEL 系 | |
|---|---|---|
| 包管理 | apt | dnf |
| 防火墙 | ufw | firewalld |
| Nginx 配置目录 | sites-available / sites-enabled | /etc/nginx/conf.d/ |
| 多一层 | 无 | SELinux |
然后是最容易漏的一步:防火墙有两道。
firewalld 是服务器里面那道墙,阿里云安全组是外面那道。两道都要放行 22 / 80 / 443。创建实例时安全组默认只开 22,现象就是“SSH 连得上,但 80 和 443 死活打不开”。补规则的位置在云控制台的实例详情里,协议选自定义 TCP,端口分别填 80 和 443。
还有 SELinux。如果你把站点放在 /www 这种非标准路径下,Nginx 会读不到文件、首页直接 403。要给那个目录打一个“允许网页服务器读”的标记,否则 Nginx 就是没权限。
Nginx:三件容易踩的事
第一,页面是目录形式的。
主题用了 trailingSlash: always,所以 /about/ 实际对应 /about/index.html。try_files 得按这个写,不然除了首页全是 404。
第二,add_header 不叠加。
这是 Nginx 一个反直觉的行为:某个 location 里一旦自己写了 add_header,从上层继承下来的那几条会被整块丢掉,不是合并。
我把安全响应头单独存成一个文件,然后每个写了 add_header 的 location 里都 include 一次。不这么做的话,现象是“404 页有安全头、首页没有”。
第三,缓存策略要分层。
带内容指纹的静态资源(/assets 下那些文件名带 hash 的)可以放心缓存一年。HTML、搜索索引、sitemap 这些不能缓存,否则改完文章推上去,线上还是旧的。
HTTPS
RHEL 系上 certbot 在 EPEL 仓库里,所以要先装 epel-release 再装它,这一步和 Ubuntu 不一样。装完跑一次它就会自动改写 Nginx 配置、加上 80 到 443 的跳转,并装好自动续期的定时器。
想连 www 子域一起签,得先把 www 的 DNS 解析配好再跑,不然证书签发会失败。
域名,三处必须一致
改域名的时候有个地方特别容易漏。站内有三处出现域名,改一处没用:
| 在哪 | 写什么 |
|---|---|
config/site.yaml 的 site | 完整地址,结尾的斜杠不能少 |
| 友链申请页那份 md | 站点链接和头像链接 |
服务器 Nginx 的 server_name | 域名加 www 变体 |
site.yaml 那处最容易出事。少个结尾斜杠、漏了 https、域名拼错一个字母,sitemap、RSS、OG 卡片和 /llms.txt 会全部指错地方,而页面本身看着完全正常。
密钥和令牌
CI 需要在两个仓库之间穿梭,靠的是几个 Secret:
| 放在哪 | 名字 | 权限 |
|---|---|---|
| 代码仓 | DEPLOY_HOST / DEPLOY_USER / DEPLOY_SSH_KEY | 服务器地址和部署私钥 |
| 代码仓 | CONTENT_REPO_TOKEN | 只读拉内容仓 |
| 内容仓 | DISPATCH_TOKEN | 读写,用来通知代码仓 |
部署用的私钥在服务器上生成,公钥留在服务器自己的 authorized_keys 里,私钥整段贴进 GitHub 的 Secret。这样私钥从头到尾没经过第三方。
回滚
部署脚本用 releases/ 加一个 current 软链:每次构建产出一个新目录,current 指向当前版本。所以回滚就是让软链指回去:
ls -1t /www/shirone/releases # 新到旧列出来ln -sfn /www/shirone/releases/<某个目录> current因为构建在 GitHub 上,磁盘占用不用太担心,每次发布大概几十到一百多 MB,保留五份也就几百 MB。
关于备案
国内服务器要备案,这是硬门槛。有个容易忽略的官方要求:审核期间网站需要处于可访问状态。
所以顺序上,先把站部署上线跑起来,再去提交备案,两边同时进行。备案号下来之后,把它填到页脚的备案位,别提前放占位文字。
上线检查清单
curl -I看首页返回 200,且cache-control是不缓存- 打开 sitemap,里面的域名是你自己的
- 随便改一篇文章推上去,等两三分钟看页面有没有变。这一步才能证明自动部署真的通了,手动点一次“运行”只能证明手动能跑
- 浏览器控制台干净
- 手机 4G 打开一次,看看横幅和字体
四篇到这里
回顾一下这条路径:
- 拆成两个仓库,把风险分开
- 内容和外观全在配置层搞定,一行源码不动
- 真需要动源码的地方,一次做完并登记
- 构建交给 CI,服务器只做托管
写这个系列的过程也挺有意思。改的时候觉得每一步都是个坑,回头看,真正难的地方在于搞清楚每个东西该放在哪一层。这件事想明白了,后面基本都是顺的。
如果这篇里某一处的现象跟你遇到的对不上,大概率是主题版本有差异,去翻一眼 src/config/<对应>Config.ts 的默认值,通常能对上。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 18 天
部分内容可能已过时