以前部署前端静态项目,我一直是本地执行:
npm run build
然后把生成的 dist 压缩、上传到宝塔,再覆盖网站目录。
项目少的时候还能接受,项目一多,这套流程就很重复:
改代码
→ 本地 build
→ 打包 dist
→ 登录宝塔
→ 上传
→ 解压
→ 覆盖旧文件
→ 检查网站
这次把部署方式改成了:
本地 git push
→ GitHub
→ GitHub Webhook
→ 宝塔 WebHook
→ 服务器拉取最新代码
→ npm build
→ 生成 dist
→ rsync 覆盖网站目录
→ 上线
整个过程看起来不复杂,但实际配置时踩了不少坑:
- GitHub 已经不支持账号密码拉取代码
- Windows 和 Linux 文件名大小写规则不一致
- 2G 服务器执行 Vite build 直接内存溢出
npm ci因package-lock.json不同步失败rsync --delete删除宝塔.user.ini时返回 code 23- 宝塔 WebHook 直接跑长时间构建容易看起来“卡住”
- GitHub Webhook URL、SSL、宝塔面板反向代理之间容易混淆
下面按实际部署过程完整记录一遍。
一、最终部署结构
这套方案适合所有能够构建成静态目录的前端项目,例如:
- Vue
- Vite
- React
- Angular
- 普通静态站
- 其他最终能生成
dist/build目录的项目
我的目录规划是:
/www/git/levi-blog-admin
保存完整源码:
/www/git/levi-blog-admin
├── .git
├── src
├── public
├── package.json
├── package-lock.json
├── vite.config.js
└── dist
宝塔真正对外提供静态网站的目录仍然保持原样:
/www/wwwroot/admin.leviqin.top
也就是说,源码和线上文件分开:
/www/git
↓
源码、node_modules、Git 仓库
↓
npm run build
↓
dist
↓
rsync
↓
/www/wwwroot
↓
Nginx 对外访问
这样不会把 .git、src、node_modules 全部放进网站根目录。
二、给服务器配置 GitHub SSH 权限
一开始直接在服务器执行 HTTPS clone:
git clone https://github.com/LeviQin/levi-blog-admin.git
GitHub 会提示:
Username for 'https://github.com':
Password for 'https://xxx@github.com':
remote: Invalid username or token.
Password authentication is not supported for Git operations.
GitHub 已经取消了 Git 操作的账号密码认证。服务器自动部署最适合用 SSH Deploy Key。
1. 服务器生成 SSH Key
执行:
ssh-keygen -t ed25519 -C "bt-levi-blog-admin"
保存位置使用默认值:
/root/.ssh/id_ed25519
部署服务器通常不需要交互输入密码,所以 passphrase 可以直接回车留空。
生成后查看公钥:
cat /root/.ssh/id_ed25519.pub
正确格式类似:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxxxxxxxxx bt-levi-blog-admin
注意:
id_ed25519.pub
才是公钥。
不要上传:
id_ed25519
如果看到:
-----BEGIN OPENSSH PRIVATE KEY-----
说明打开的是私钥,绝对不要提交到 GitHub,也不要公开。
2. 添加 GitHub Deploy Key
进入仓库:
GitHub
→ Repository
→ Settings
→ Deploy keys
→ Add deploy key
填写:
Title:
Tencent Cloud BT
Key 填刚才 cat ~/.ssh/id_ed25519.pub 得到的完整内容。
服务器只需要 clone / fetch / pull,所以 Allow write access 不需要勾选。
3. 多个仓库部署时,可以改用账号级 SSH Key
如果这台服务器只部署一个仓库,继续使用 Deploy Key 就够了。
但如果同一台宝塔服务器后面还要部署多个自己账号下的 GitHub 仓库,例如:
levi-blog-admin
levi-blog
其他前端项目
其他 Node 项目
每个仓库都单独添加 Deploy Key 会比较麻烦。
这种情况下,可以给服务器单独生成一把 GitHub 账号级 SSH Key。
账号级 SSH Key 配置在 GitHub 个人账号中,而不是某个仓库中。
两种方式的区别可以简单理解为:
| 方式 | 配置位置 | 权限范围 | 适合场景 |
|---|---|---|---|
| Deploy Key | Repository → Settings → Deploy keys | 单个仓库 | 单项目、权限隔离要求高 |
| 账号级 SSH Key | GitHub → Settings → SSH and GPG keys | 当前账号有权限访问的仓库 | 一台服务器部署多个自己的项目 |
如果对权限隔离要求比较高,还是推荐一仓库一把 Deploy Key。
如果服务器本身就是自己的固定部署机,并且需要维护很多个人仓库,账号级 SSH Key 会方便很多。
3.1 生成一把专门的账号级 SSH Key
为了避免和前面的 Deploy Key 混在一起,建议重新生成一把单独的 Key:
ssh-keygen -t ed25519 \
-C "tencent-bt-github-account" \
-f /root/.ssh/id_ed25519_github
用于自动部署时,passphrase 可以直接回车留空。
生成完成后会得到:
/root/.ssh/id_ed25519_github
/root/.ssh/id_ed25519_github.pub
其中 id_ed25519_github 是私钥,不要公开;id_ed25519_github.pub 是公钥,可以添加到 GitHub。
查看公钥:
cat /root/.ssh/id_ed25519_github.pub
输出类似:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxxxxxxxxx tencent-bt-github-account
复制整行。
3.2 添加到 GitHub 个人账号
进入 GitHub:
右上角头像
→ Settings
→ SSH and GPG keys
→ New SSH key
这里和 Deploy Key 最大的区别是:
Deploy Key:
Repository → Settings → Deploy keys
账号级 SSH Key:
个人账号 → Settings → SSH and GPG keys
填写:
Title:
Tencent Cloud BT Server
Key type:
Authentication Key
Key 粘贴刚才 id_ed25519_github.pub 的完整内容,然后保存。
3.3 让服务器固定使用这把 Key
编辑:
vi /root/.ssh/config
加入:
Host github.com
HostName github.com
User git
IdentityFile /root/.ssh/id_ed25519_github
IdentitiesOnly yes
设置权限:
chmod 700 /root/.ssh
chmod 600 /root/.ssh/config
chmod 600 /root/.ssh/id_ed25519_github
chmod 644 /root/.ssh/id_ed25519_github.pub
这样以后:
git clone git@github.com:你的账号/仓库A.git
git clone git@github.com:你的账号/仓库B.git
都会优先使用 /root/.ssh/id_ed25519_github。
3.4 测试账号级 SSH 是否生效
执行:
ssh -T git@github.com
正常会看到类似:
Hi your-github-name! You've successfully authenticated,
but GitHub does not provide shell access.
如果想确认某个私有仓库是否有访问权限,不需要真的 Clone,可以直接:
git ls-remote git@github.com:你的账号/仓库名.git
如果返回 HEAD、refs/heads/main 等 refs,说明这台服务器已经可以访问这个仓库。
3.5 之前的 Deploy Key 要不要删除
不需要马上删除。可以先保留原来的 Deploy Key,确认账号级 SSH Key 已经能够正常访问多个仓库。全部测试通过以后,再根据需要删除旧 Deploy Key。
需要注意的是,账号级 SSH Key 的权限范围比单仓库 Deploy Key 大。如果服务器私钥泄露,影响范围也会更大。
因此可以这样选:
单仓库生产服务器
→ 更推荐 Deploy Key
自己的多项目宝塔服务器
→ 可以使用账号级 SSH Key
两种方案没有绝对优劣,主要看服务器用途和权限隔离要求。
4. 测试 SSH
服务器执行:
ssh -T git@github.com
第一次会提示:
The authenticity of host 'github.com' can't be established.
Are you sure you want to continue connecting?
输入:
yes
成功后会出现类似:
You've successfully authenticated, but GitHub does not provide shell access.
说明服务器已经可以读取 GitHub 仓库。
三、Clone 源码到单独目录
创建统一源码目录:
mkdir -p /www/git
cd /www/git
Clone:
git clone git@github.com:LeviQin/levi-blog-admin.git
进入项目:
cd /www/git/levi-blog-admin
确认 remote:
git remote -v
应该类似:
origin git@github.com:LeviQin/levi-blog-admin.git (fetch)
origin git@github.com:LeviQin/levi-blog-admin.git (push)
四、先在服务器把项目手动构建通
不要一上来就配置 Webhook。先确保下面这条链路手动执行没有问题:
GitHub
→ 服务器
→ 安装依赖
→ build
→ dist
这个项目同时存在过:
package-lock.json
yarn.lock
但服务器最终统一使用 npm。
如果决定使用 npm,建议最终只保留:
package.json
package-lock.json
避免 npm 和 Yarn 的锁文件长期混用。
安装依赖:
npm ci --include=dev --no-audit --no-fund
构建:
npm run build
五、Windows 正常,Linux build 报路径不存在
这个项目在 Windows 开发时一直正常,但放到 Linux 后连续出现:
Could not resolve './../views/Dashboard/Index.vue'
Could not resolve './../views/WebSites/NaviList.vue'
Could not resolve './../views/M/Article/ArticleList.vue'
Could not load src/components/Editor/Index.vue
ENOENT: no such file or directory
原因不是 Vite,而是 Windows 默认不严格区分文件名大小写,Linux 区分。
例如:
Dashboard
dashboard
Windows 上可能都能访问,Linux 下是两个不同目录。
查看 Git 真正记录的文件名
不要只看 Windows 文件管理器。
执行:
git ls-files src/views
或者服务器:
git ls-tree -r HEAD --name-only
例如代码写的是:
import("@/views/Dashboard/Index.vue")
但 Git 实际记录:
src/views/dashboard/Index.vue
Linux 构建一定失败。
Windows 修改纯大小写必须用 git mv
直接在资源管理器把:
dashboard
改成:
Dashboard
Git 可能完全检测不到。
正确方式:
git mv src/views/dashboard src/views/__tmp_dashboard
git mv src/views/__tmp_dashboard src/views/Dashboard
文件名也是一样。
例如:
articleList.vue
改成:
ArticleList.vue
执行:
git mv src/views/M/Article/articleList.vue src/views/M/Article/__tmp_article.vue
git mv src/views/M/Article/__tmp_article.vue src/views/M/Article/ArticleList.vue
然后:
git status
git add -A
git commit -m "fix: normalize path casing"
git push
六、GitHub 已经更新,服务器为什么还是旧代码
中间还遇到过一种情况:GitHub 已经是:
component: () => import("@/views/Websites/NaviList.vue")
服务器还是:
component: () => import("@/views/WebSites/NaviList.vue")
检查:
git rev-parse HEAD
git rev-parse origin/main
发现两个 SHA 不一致。
原因是:
git fetch origin
只更新:
origin/main
不会自动更新当前工作区。
部署服务器更适合直接:
git fetch origin
git reset --hard origin/main
这样服务器永远以 GitHub main 为准。
部署目录不要手工改代码,也不需要 merge。
七、2G 服务器执行 Vite build 内存溢出
路径问题修完后,项目终于进入真正构建阶段,但又出现:
FATAL ERROR: Ineffective mark-compacts near heap limit
Allocation failed - JavaScript heap out of memory
这个项目构建时大约有:
2700+ modules
138 chunks
同时还开启了:
vite-plugin-imageminvite-plugin-compression- Element Plus
- ECharts
- 自动组件导入
- 图标自动导入
2G 服务器压力很大。
八、给 Vite 增加低内存构建模式
不要单纯把 Node 内存无限调大。
2G 服务器如果直接把 Heap 拉满,Node 可能把整台机器内存吃光,宝塔、Nginx、SSH 都会受到影响。
最终给 Node Heap 控制在:
1400MB
同时关闭服务器构建时不必要的插件。
vite.config.js
先定义:
const isLowMemoryBuild = process.env.LOW_MEMORY_BUILD === "true";
图片压缩插件:
!isLowMemoryBuild &&
viteImagemin({
gifsicle: {
optimizationLevel: 3,
interlaced: false,
},
optipng: {
optimizationLevel: 5,
},
mozjpeg: {
quality: 80,
progressive: true,
},
svgo: {
plugins: [
{
name: "removeViewBox",
active: false,
},
{
name: "removeEmptyAttrs",
active: true,
},
],
},
})
gzip 插件也可以在服务器低内存模式关闭:
!isLowMemoryBuild &&
viteCompression({
algorithm: "gzip",
threshold: 10240,
})
插件数组最后加:
.filter(Boolean)
build 配置建议
build: {
outDir: "dist",
emptyOutDir: true,
minify: "esbuild",
reportCompressedSize: false,
cssCodeSplit: true,
sourcemap: false,
manifest: false,
chunkSizeWarningLimit: 1500,
assetsDir: "static/img/",
rollupOptions: {
output: {
chunkFileNames: "static/js/[name].[hash].js",
entryFileNames: "static/js/[name].[hash].js",
assetFileNames: "static/[ext]/[name].[hash].[ext]",
},
},
}
其中比较重要的是:
reportCompressedSize: false
服务器没必要每次构建再计算 gzip 后的尺寸。
如果 Nginx 已经配置 gzip,也没有必要每次 Vite 构建再生成 .gz 文件。
图片同样没必要每次服务器部署都重新压缩。
九、让 npm run build 自动使用低内存模式
为了不用每次执行:
LOW_MEMORY_BUILD=true NODE_OPTIONS="--max-old-space-size=1400" npm run build
安装:
npm install -D cross-env
package.json:
{
"scripts": {
"dev": "vite",
"build": "cross-env LOW_MEMORY_BUILD=true NODE_OPTIONS=--max-old-space-size=1400 vite build",
"build:full": "vite build"
}
}
以后服务器仍然只需要:
npm run build
本地如果想执行完整构建:
npm run build:full
十、cross-env 安装后 npm ci 报错
服务器执行:
npm ci
出现:
npm ERR! `npm ci` can only install packages when your package.json
and package-lock.json are in sync.
Missing: cross-env@10.1.0 from lock file
原因很简单:package.json 已经增加了 cross-env,但是 package-lock.json 没有同步。
在本地执行:
npm install -D cross-env
然后把两个文件一起提交:
git add package.json package-lock.json
git commit -m "build: sync cross-env dependency"
git push origin main
服务器再:
git fetch origin
git reset --hard origin/main
npm ci --include=dev --no-audit --no-fund
就可以正常安装。
这也是为什么生产部署推荐使用:
npm ci
而不是随意 npm install。
npm ci 会强制要求锁文件和依赖声明完全一致。
十一、不要每次部署都 npm ci
2G 服务器每次:
删 node_modules
→ 重新 npm ci
→ build
压力很大。
实际部署时可以比较 package-lock.json 是否变化。
只有依赖变化时才执行:
npm ci
普通修改 .vue、.js、.css 时直接使用现有 node_modules 构建。
十二、构建完成后先检查 dist
Vite 构建失败时,emptyOutDir: true 会先清空 dist。
然后 public 文件可能已经复制进去,但 Rollup 后续失败。
这时 dist 可能只剩:
images/
set-logo.svg
set-logo1.svg
set-logo2.svg
却没有:
index.html
static/js/
static/css/
所以自动发布前一定检查:
test -f dist/index.html
这是整个自动部署脚本里非常重要的一道保护。
如果构建失败,绝对不能继续:
rsync --delete
否则可能把线上网站清空。
十三、使用 rsync 发布 dist
手动构建成功后,先备份现有网站:
mkdir -p /www/backup
tar -czf /www/backup/admin.leviqin.top-$(date +%Y%m%d-%H%M%S).tar.gz \
/www/wwwroot/admin.leviqin.top
同步:
rsync -av --delete \
--exclude='.well-known/' \
--exclude='.user.ini' \
/www/git/levi-blog-admin/dist/ \
/www/wwwroot/admin.leviqin.top/
注意 dist/ 后面的 / 很重要,表示复制 dist 里面的内容。
最终:
/www/wwwroot/admin.leviqin.top/
├── index.html
├── static
├── images
└── ...
而不是:
/www/wwwroot/admin.leviqin.top/dist/
十四、rsync code 23:宝塔 .user.ini 无法删除
第一次执行:
rsync -av --delete
最后出现:
rsync error: some files/attrs were not transferred
(code 23)
查看日志:
grep -nE "rsync:|failed|denied|Operation not permitted|Permission denied" \
/www/logs/levi-blog-admin-deploy.log | tail -30
找到:
rsync: [generator] delete_file: unlink(.user.ini)
failed: Operation not permitted (1)
原因是宝塔对 .user.ini 增加了保护属性。
dist 中没有这个文件,rsync --delete 想删除它,结果失败。
不要删除 .user.ini,直接排除:
--exclude='.user.ini'
最终:
rsync -av --delete \
--exclude='.well-known/' \
--exclude='.user.ini' \
"$PROJECT_DIR/dist/" \
"$WEB_DIR/"
不要用:
rsync ... || true
强行吞掉错误。真正有 JS 或 CSS 文件传输失败时,也会被误判成成功。
十五、最终部署脚本
创建:
/www/scripts/deploy-levi-blog-admin.sh
内容:
#!/bin/bash
(
flock -n 9 || {
echo "[$(date)] 已有部署任务正在运行,本次退出"
exit 1
}
set -e
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
PROJECT_DIR="/www/git/levi-blog-admin"
WEB_DIR="/www/wwwroot/admin.leviqin.top"
echo ""
echo "========================================"
echo "Levi Blog Admin Deploy"
echo "Start: $(date)"
echo "User: $(whoami)"
echo "========================================"
cd "$PROJECT_DIR"
echo "[1/6] 获取最新代码"
OLD_LOCK=$(sha256sum package-lock.json 2>/dev/null | awk '{print $1}')
git fetch origin
git reset --hard origin/main
NEW_LOCK=$(sha256sum package-lock.json 2>/dev/null | awk '{print $1}')
echo "当前版本:$(git rev-parse --short HEAD)"
echo "[2/6] 检查依赖"
if [ ! -d "node_modules" ] || [ "$OLD_LOCK" != "$NEW_LOCK" ]; then
echo "依赖发生变化,执行 npm ci"
npm ci \
--include=dev \
--no-audit \
--no-fund \
--prefer-offline
else
echo "依赖未变化,跳过 npm ci"
fi
echo "[3/6] 清理旧构建"
rm -rf "$PROJECT_DIR/dist"
echo "[4/6] 构建"
npm run build
echo "[5/6] 检查构建产物"
if [ ! -f "$PROJECT_DIR/dist/index.html" ]; then
echo "ERROR: dist/index.html 不存在"
exit 1
fi
echo "构建成功:"
ls -lh "$PROJECT_DIR/dist/index.html"
du -sh "$PROJECT_DIR/dist"
echo "[6/6] 发布"
rsync -av --delete \
--exclude='.well-known/' \
--exclude='.user.ini' \
"$PROJECT_DIR/dist/" \
"$WEB_DIR/"
echo "========================================"
echo "Deploy Success"
echo "End: $(date)"
echo "========================================"
) 9>/tmp/levi-blog-admin-deploy.lock
加执行权限:
chmod +x /www/scripts/deploy-levi-blog-admin.sh
先手动测试:
/www/scripts/deploy-levi-blog-admin.sh
看到:
Deploy Success
再继续配置 WebHook。
十六、为什么脚本里要用 flock
服务器只有 2G。
如果连续 push 两次:
04:00 push
04:01 push
很可能出现两个 vite build 同时运行。
结果就是:
内存耗尽
→ 宝塔失联
→ SSH 卡顿
→ OOM
所以使用:
flock
确保同一时间只存在一个部署任务。
第二次触发时:
已有部署任务正在运行,本次退出
比把整台服务器拖死安全得多。
十七、宝塔 WebHook 不直接运行完整构建
最开始把完整构建脚本直接放进宝塔 WebHook。
日志停在:
[3/5] 构建
[4/5] 检查构建产物
但没有继续。
对于构建时间比较长的项目,不建议让 HTTP Webhook 请求一直等待:
git
→ npm
→ Vite
→ rsync
更稳定的方式是:
WebHook
→ 启动后台脚本
→ 立即返回
宝塔 WebHook 中只放:
#!/bin/bash
nohup /bin/bash /www/scripts/deploy-levi-blog-admin.sh \
>> /www/logs/levi-blog-admin-deploy.log 2>&1 &
echo "部署任务已启动,PID: $!"
准备日志目录:
mkdir -p /www/logs
这样 WebHook 请求很快就结束,真正部署过程在后台继续。
十八、查看部署日志
实时查看:
tail -f /www/logs/levi-blog-admin-deploy.log
正常会看到:
Levi Blog Admin Deploy
Start: ...
[1/6] 获取最新代码
当前版本:xxxxxxx
[2/6] 检查依赖
依赖未变化,跳过 npm ci
[3/6] 清理旧构建
[4/6] 构建
vite ...
✓ xxxx modules transformed
✓ built in xx.xx s
[5/6] 检查构建产物
构建成功
[6/6] 发布
Deploy Success
退出:
Ctrl + C
只会关闭 tail,不会停止部署。
查看最近日志:
tail -100 /www/logs/levi-blog-admin-deploy.log
十九、增加 WebHook 触发日志
如果 GitHub push 后没有部署,很难第一时间判断:
GitHub 没有触发?
宝塔没收到?
脚本没启动?
可以给宝塔 WebHook 再增加一份非常简单的触发日志:
#!/bin/bash
echo "========================================" >> /www/logs/webhook-trigger.log
echo "Webhook triggered: $(date)" >> /www/logs/webhook-trigger.log
echo "User: $(whoami)" >> /www/logs/webhook-trigger.log
nohup /bin/bash /www/scripts/deploy-levi-blog-admin.sh \
>> /www/logs/levi-blog-admin-deploy.log 2>&1 &
echo "PID: $!" >> /www/logs/webhook-trigger.log
echo "部署任务已启动,PID: $!"
以后:
tail -20 /www/logs/webhook-trigger.log
只要有新记录,就说明:
GitHub
→ 宝塔 WebHook
至少已经成功触发。
二十、配置 GitHub Webhook
宝塔 WebHook 插件会生成一个类似:
https://example.com/hook?access_key=xxxxxxxx
的地址。
GitHub:
Repository
→ Settings
→ Webhooks
→ Add webhook
配置:
Payload URL:
宝塔生成的完整 WebHook 地址
Content type:
application/json
事件:
Just the push event
启用:
Active
如果没有自行实现 GitHub X-Hub-Signature-256 校验,Secret 可以留空。宝塔本身已经使用 access_key 作为触发凭证。
二十一、GitHub Webhook SSL 验证
GitHub Webhook 默认会验证 HTTPS 证书。
正式环境建议保持:
Enable SSL verification
如果 GitHub Recent deliveries 出现 SSL 错误,不要长期关闭 SSL 验证。
应该检查:
openssl s_client \
-connect baota.example.com:443 \
-servername baota.example.com \
</dev/null 2>/dev/null \
| openssl x509 \
-noout \
-subject \
-issuer \
-dates \
-ext subjectAltName
Subject Alternative Name 中必须包含:
DNS:baota.example.com
同时检查 Nginx 真正使用的证书:
nginx -T 2>/dev/null \
| grep -A 20 -B 5 "server_name baota.example.com"
确认:
ssl_certificate
ssl_certificate_key
指向正确证书。
二十二、access_key 不要公开
宝塔 WebHook URL 中的:
access_key
相当于部署触发凭证。
不要放在:
- GitHub README
- 博客截图
- 公开日志
- 前端代码
- 公开仓库
如果不小心暴露,直接重新生成。
二十三、正式使用后的操作
配置完成以后,本地日常开发只需要:
git add .
git commit -m "feat: update"
git push origin main
剩下全部自动完成:
git push
↓
GitHub main
↓
GitHub Webhook
↓
宝塔 WebHook
↓
nohup 启动后台部署
↓
git fetch origin
↓
git reset --hard origin/main
↓
检查 package-lock
↓
必要时 npm ci
↓
npm run build
↓
检查 dist/index.html
↓
rsync
↓
网站更新
二十四、排错顺序
自动部署失败后,不建议一上来就改配置。按链路从前往后排查。
1. GitHub 是否真的有新 commit
服务器:
git rev-parse HEAD
git ls-remote origin refs/heads/main
两个 SHA 不一样,说明服务器没有部署最新版本。
2. GitHub 有没有触发 Webhook
GitHub:
Settings
→ Webhooks
→ Recent deliveries
常见状态:
200 / 204
说明 GitHub 已经成功请求。
404
Webhook URL 不正确。
403
可能被安全规则或权限拦截。
Timeout
GitHub 无法访问宝塔。
SSL error
证书有问题。
3. 宝塔有没有收到请求
tail -20 /www/logs/webhook-trigger.log
没有新记录,说明 GitHub → 宝塔 这一段有问题。
4. 部署脚本有没有启动
ps -ef | grep deploy-levi-blog-admin | grep -v grep
5. 查看真实部署日志
tail -100 /www/logs/levi-blog-admin-deploy.log
6. Git 是否真正同步
git fetch origin
git rev-parse HEAD
git rev-parse origin/main
不一致:
git reset --hard origin/main
7. dist 是否完整
test -f dist/index.html && echo "dist 正常" || echo "dist 异常"
8. rsync 是否失败
grep -nE \
"rsync:|failed|denied|Operation not permitted|Permission denied" \
/www/logs/levi-blog-admin-deploy.log \
| tail -30
二十五、几个比较关键的经验
源码和网站目录分开
不要:
/www/wwwroot/example.com
├── src
├── node_modules
├── .git
└── dist
更推荐:
/www/git/project
负责源码和构建。
/www/wwwroot/example.com
只放最终静态文件。
部署服务器不要改源码
服务器应该永远:
git fetch origin
git reset --hard origin/main
GitHub main 才是唯一代码源。
构建成功不等于 dist 一定完整
发布前必须:
test -f dist/index.html
rsync --delete 一定配 exclude
宝塔目录至少考虑:
.user.ini
.well-known/
低配置服务器不要做没必要的构建工作
2G 服务器上:
- 图片压缩尽量关闭
- gzip 构建尽量交给 Nginx
- sourcemap 关闭
- compressed size 关闭
- Node Heap 留出系统运行空间
- npm ci 只在依赖变化时执行
长时间部署不要阻塞 WebHook 请求
Webhook:
收到请求
→ nohup
→ 立即返回
真正构建放后台。
一定防止重复构建
flock
非常有用。
低配置服务器同时跑两个 Vite build,很容易直接把机器拖死。
结尾
以前手动上传 dist 时,部署这件事看起来很简单。
真正改成自动部署以后才会发现,它其实涉及:
Git
SSH
GitHub
Linux
Node
npm
Vite
Nginx
rsync
宝塔
Webhook
SSL
每一层都可能成为问题来源。
但把链路拆开以后就容易很多:
GitHub 能不能拉?
↓
服务器能不能 build?
↓
dist 对不对?
↓
rsync 能不能发布?
↓
宝塔 WebHook 能不能启动脚本?
↓
GitHub Webhook 能不能请求宝塔?
每一步单独测试通过,再把它们串起来。
最终得到的效果就是:
git push
代码提交完成后,服务器自动拉取、构建并上线。
对需要经常更新的前端项目来说,这套流程比手工上传 dist 省事很多,也更不容易因为漏删旧文件、上传错目录或者忘记构建而出问题。
private note
交流
文章暂不开放公开评论。如果你有想法、问题或建议,欢迎私下联系站长。
联系站长 →