静态站部署最简单的方式就是 rsync 推 dist/。但 --delete 这个参数用不好,可以让人印象深刻。
尾斜杠决定了同步的是目录还是内容
这是 rsync 最经典的坑:
rsync -av dist/ user@host:/var/www/site/ # 同步 dist 里的内容
rsync -av dist user@host:/var/www/site/ # 把 dist 目录本身放进去
差一个斜杠,第二条会在服务器上创建 /var/www/site/dist/,然后你打开网站看到 403,对着 Nginx 配置查半天。
记法:源路径带斜杠 = “这个目录里的东西”,不带 = “这个目录”。目标路径的斜杠没有这个区别。
养成源路径永远带斜杠的习惯,能省掉一整类问题。
—delete 会删掉不属于构建产物的文件
--delete 的作用是让目标端与源端完全一致——源端没有的,目标端就删掉。这正是部署想要的:删掉的页面应该真的消失,而不是变成幽灵路由留在服务器上。
但”完全一致”是字面意思。任何你手动放到服务器上、而不在构建产物里的文件,都会被清掉。
典型的翻车场景:某个第三方平台要你放一个验证文件到网站根目录,你顺手 scp 了上去。下一次部署,文件没了,验证失效——而且你多半不会立刻发现。页脚引用的一张图标也是同理,下次部署后变成裂图。
正确做法是让这类文件进入构建产物。Astro 里就是放进 public/,构建时会原样复制到 dist/ 根目录。
public/verify.txt → dist/verify.txt
判断标准很简单:这个文件应该被版本控制吗?应该,那它就该在仓库里,而不是只存在于服务器上。
先演练再执行
--delete 的破坏性操作值得先看一眼。加 -n(等价于 --dry-run):
rsync -avn --delete dist/ user@host:/var/www/site/
输出里以 deleting 开头的行就是即将被删的文件。扫一眼,确认没有意外,再去掉 -n 真跑。
这一步在第一次部署、或者改了构建配置之后特别值得做。日常增量部署可以省。
排除不该同步的东西
.DS_Store 是 macOS 用户的老朋友,会跟着 rsync 一路传到服务器上。
rsync -avz --delete \
--exclude='.DS_Store' \
--exclude='.git' \
dist/ user@host:/var/www/site/
服务器端的 Nginx 也该兜一层,拒绝一切隐藏文件:
location ~ /\. {
deny all;
}
两边都做。客户端的 exclude 可能被别人的部署脚本绕过,服务器端的规则是最后一道。
-z 不是永远该加
-z 开启传输压缩,在慢速链路上有用。但如果你的产物已经是压缩过的格式——图片、字体、预压缩的 .br 文件——再压一遍只是浪费 CPU,传输量几乎不变。
-avz 是网上抄来最多的组合,但同步一堆 PNG 和 woff2 的时候,-av 可能更快。局域网或者同机房内网基本没必要加 -z。
最后落到一条命令
把这些整理进部署脚本,同步之前先做几项自检:
#!/usr/bin/env bash
set -euo pipefail
npm run build
# 构建产物长得不对就别往上推
[ -f dist/index.html ] || { echo "✗ 首页缺失"; exit 1; }
grep -q '<title>' dist/index.html || { echo "✗ 首页没有 title"; exit 1; }
[ "$(find dist -name '*.html' | wc -l)" -gt 1 ] || { echo "✗ 页面数异常"; exit 1; }
rsync -av --delete --exclude='.DS_Store' dist/ user@example.com:/var/www/site/
set -euo pipefail 那行是关键——任何一步失败就停,不会带着半个构建产物同步上去。