本篇是《手写一个内容型技术博客站》系列第 8 篇。
代码写完了,最刺激的部分来了:上线。这篇讲从阿里云 ECS 到域名全链路的部署实践,重点是不踩坑——有些坑踩一次,数据和服务能疼很久。
一、部署拓扑总览
用户浏览器
│ https 443
▼
Apache(反向代理 + HTTPS 证书)
│ http 127.0.0.1:<端口>
▼
Node (next start, pm2 托管)
│ 读文件
▼
src/content/ + src/data/(磁盘)为什么 Apache 挡在 Node 前面:让 Apache 处理 443/证书/静态资源/安全头,Node 专注业务。Next.js 的 next start 直接暴露公网不是不行,但把透传、缓存、防护都交给久经考验的 Apache/Nginx 更稳。
二、铁律:node_modules 永不拷/rsync 上传
这是部署最重要的纪律。
为什么:node_modules 里有大量平台相关二进制(@next/swc 等)和符号链接(.bin/next)。从本机拷贝/rsync 上传时不加 -l,符号链接会变成普通坏文件;跨平台二进制不兼容。服务器跑 next 时:
Cannot find module '../server/require-hook'
Require stack: .../node_modules/.bin/next就是这个根因——.bin/next 软链坏了。
正确做法:
# 代码同步(排除 node_modules/.next/运行时数据)
rsync -avz --exclude node_modules --exclude .next \
--exclude 'src/data/comments' ./ user@host:/srv/nextjs-jiusi/
# 服务器上装依赖(保留软链 + postinstall)
cd /srv/nextjs-jiusi
rm -rf node_modules && npm ci铁律:部署流程只 rsync 代码,依赖一律服务器 npm ci。
三、进程管理:pm2
Node 服务用 pm2 托管,好处是崩溃自动重启、日志轮转、开机自启。
# 启动(关键:--update-env 让 pm2 重载环境变量)
pm2 start npm --name nextjs-jiusi -- start
pm2 restart nextjs-jiusi --update-env
# 开机自启
pm2 startup
pm2 save日志轮转
pm2 日志无限增长会撑爆磁盘。用 pm2-logrotate 或系统 logrotate:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30配置要点(skill 实测):size 10M + daily 双触达——只配 size 不配 daily,日志量小则 main log 长期不轮转,maxage 清理永不执行。
四、Apache 反向代理
# /etc/apache2/sites-available/your-site.conf
<VirtualHost *:443>
ServerName chahuawu.com
# HTTPS 证书配置...
# 反向代理到本地 Node
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8000/
ProxyPassReverse / http://127.0.0.1:8000/
# 透传真实 IP(Node 做 IP 去重需要)
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>X-Forwarded-For 必须设——评论/IP 去重、访问量统计依赖真实 IP,Apache 反代不设的话 Node 看到的是 127.0.0.1。
五、自动重建:auto_rebuild.py
"新增文章要 build"不能靠 SSH 手动。auto_rebuild.py 轮询 src 变化自动构建重启:
# scripts/auto_rebuild.py
# 轮询 3s,监控 src/ + public/ + next.config.ts + package.json + tsconfig.json
# 排除 src/data/comments(运行时数据,写入不触发重建)
# 检测到变化 → rm -rf .next && next build → 重启 server配合 systemd 或 nohup 常驻。这样生产上新内容 = 上传 md 文件 → 自动 build → 刷新可见。
六、同源问题:本地好好的,线上 404
这个坑几乎每个人都会遇到,讲透它。
症状:本地 next start 一切正常,上传后线上某个详情页 404。
真相:Next.js 的 SSG 详情页(文章、静态页)是 build 时预渲染的。如果你本件构建的产物(.next)没有同步到服务器,或者线上 build 时内容没到位,服务器就没有对应 slug 的预渲染 HTML。
关键:SSG 页面必须 build 时才生成,且 dynamicParams=false 时未枚举的 slug 永远 404。
排查顺序:
# 1. 确认线上 .next 是线上 build 的,不是拷贝的
ls .next/server/app/posts/ # 看有没有对应 slug 的 html
# 2. 确认线上内容文件到位
ls src/content/categories/.../<slug>.md
# 3. 确认 build 是 rm -rf .next 后的干净构建ISR 救不了新 slug:revalidate=600 只重建"已枚举 slug"的内容,不会重新枚举 generateStaticParams。新增文章后必须完整 rebuild。
上线顺序纪律:先 build + 重启,再让新内容可点击;否则列表(动态)能点进去,详情页(SSG)404。
七、部署检查清单
□ 代码同步:rsync 代码(排除 node_modules/.next/运行时数据)
□ 服务器 npm ci:rm -rf node_modules && npm ci
□ 环境变量:.env.local / 环境变量就位(Basic Auth 凭据等)
□ 干净构建:rm -rf .next && npm run build
□ 数据备份:src/data/(评论/阅读数/访问统计)先备份再覆盖
□ 启动与守护:pm2 restart --update-env
□ 冒烟测试:curl 关键路由(首页/文章/教程/API)
□ 日志:大小/保留天数配置部署的本质:把你的"本地能跑"变成"线上能持续跑"。多一份 checklist,少一次深夜事故。
给普通人的一句话:上线不是一个动作,而是一套"把你的代码安全地搬进生产环境并让它活下来"的纪律。
参考
- Next.js 部署文档:https://nextjs.org/docs/app/building-your-application/deploying
评论
0评论加载中…