很多人能本地跑通 Next.js 项目,却在"部署到自己的服务器"这一步卡住:教程零散,每一步都只说一半。这篇文章把完整链路串起来,每一步都给出可直接复制的命令。示例统一使用 example.com 这个域名和一台 1 核 2G 的阿里云 ECS(其他云厂商同理)。
准备工作清单
- 一台云服务器(Linux,本文以 Alibaba Cloud Linux 3 为例,Ubuntu 命令几乎相同)
- 一个域名,且能管理它的 DNS 解析
- 本地能跑通
npm run build的项目 - 云服务控制台里放行 80 和 443 端口(安全组)
一、域名与 DNS 解析
在域名服务商控制台添加一条 A 记录:主机记录填子域名(比如 ai,即 ai.example.com),记录值填服务器公网 IP。主域名想直接用就填 @,带 www 就再加一条 www。
解析生效通常几分钟到半小时。本地验证:
ping ai.example.com # 应解析到你的服务器 IP
二、ICP 备案注意事项
服务器在中国大陆境内,域名必须完成 ICP 备案才能通过 80/443 端口正常访问,否则云厂商会拦截请求。几点经验:
- 备案在服务器所属云厂商的备案系统提交,主体与域名所有者保持一致
- 首次备案周期约 1–3 周,期间网站不能提前开通访问
- 子域名跟随主域名备案,一般无需单独备案
- 如果只是测试,可以先用境外服务器免备案跑通流程,再迁回国内
三、SSH 登录
推荐使用密钥登录而非密码。本地生成密钥(已有可跳过),把公钥放到服务器:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_myserver
ssh-copy-id -i ~/.ssh/id_ed25519_myserver.pub root@你的服务器IP
ssh -i ~/.ssh/id_ed25519_myserver root@你的服务器IP
四、安装 Node.js(国内镜像加速)
官方源在国内很慢,用 npmmirror 的二进制镜像:
cd /tmp
curl -fsSL -o node.tar.xz https://registry.npmmirror.com/-/binary/node/latest-v22.x/node-v22.20.0-linux-x64.tar.xz
tar -xJf node.tar.xz -C /opt
ln -sf /opt/node-v22.20.0-linux-x64/bin/node /usr/local/bin/node
ln -sf /opt/node-v22.20.0-linux-x64/bin/npm /usr/local/bin/npm
node --version # 应输出 v22.x
注意:版本号会随时间更新,先去镜像目录列表确认最新的 v22 文件名。
五、小内存服务器加 Swap
2G 以下内存的机器跑 npm run build 大概率 OOM(内存耗尽被kill)。加 2G Swap 兜底:
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab # 重启后自动挂载
free -h # Swap 行应显示 2.0Gi
六、rsync 上传代码
不要用 FTP,也不要把 node_modules 传上去。在本地项目目录执行:
rsync -az --delete -e "ssh -i ~/.ssh/id_ed25519_myserver" \
--exclude node_modules --exclude .next --exclude data --exclude .git \
./ root@你的服务器IP:/var/www/myapp/
--delete 让服务器目录与本地保持一致;排除项里 data 是线上数据库目录,绝不能被本地覆盖。
七、安装依赖与构建
cd /var/www/myapp
npm ci --registry=https://registry.npmmirror.com
npm run build
如果项目含 better-sqlite3 这类原生模块且预编译包下载失败,先装编译工具链再重试:dnf install -y gcc-c++ make python3(Ubuntu 用 apt install -y build-essential python3)。
生产环境的环境变量写在 .env.production(如密钥、站点 URL),密钥用 openssl rand -hex 32 生成,不要提交进 Git。
八、systemd 常驻服务
直接 npm start 关掉 SSH 就停了。用 systemd 托管,崩了自动重启、开机自启。创建 /etc/systemd/system/myapp.service:
[Unit]
Description=My Next.js app
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/www/myapp
EnvironmentFile=/var/www/myapp/.env.production
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/local/bin/npm start
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
启用并验证:
systemctl daemon-reload
systemctl enable --now myapp
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000 # 期望 200
九、nginx 反向代理
创建 /etc/nginx/conf.d/myapp.conf,完整 server 块:
server {
listen 80;
server_name ai.example.com;
client_max_body_size 10m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
client_max_body_size 决定了上传文件的大小上限,默认 1m 很容易踩坑。改完 nginx -t 测试语法,再 systemctl reload nginx。
十、certbot 申请免费 HTTPS 证书
服务器装好 certbot 后一条命令搞定,它会自动改 nginx 配置并加上 80→443 跳转:
certbot --nginx -d ai.example.com --redirect --non-interactive --agree-tos -m you@example.com
Let's Encrypt 证书有效期 90 天,certbot 安装时会自动注册续期定时任务。用 certbot renew --dry-run 可以预演续期是否正常。
上线验证清单
逐项过一遍再宣布上线:
https://你的域名/返回 200,浏览器有锁标志http://自动跳转https://- 关键页面(列表页、详情页、登录)逐个点开
- sitemap.xml / robots.txt 里的 URL 是正式域名而不是 localhost
- 用错误密码登录一次,确认返回 401 而不是 500
- 服务器上
systemctl status myapp是 active (running)
日常更新流程
以后每次发版就三步:
# 1. 本地同步代码(排除项与首次上传相同)
rsync -az --delete -e "ssh -i ~/.ssh/id_ed25519_myserver" \
--exclude node_modules --exclude .next --exclude data --exclude .git \
./ root@你的服务器IP:/var/www/myapp/
# 2. 服务器上重新构建
ssh root@你的服务器IP 'cd /var/www/myapp && npm ci && npm run build'
# 3. 重启服务
ssh root@你的服务器IP 'systemctl restart myapp'
依赖没变时可以跳过 npm ci。页面若是动态渲染(force-dynamic),纯内容更新甚至不需要重新构建。
常见问题
Q:构建时卡住然后进程消失? 基本是内存不足,看 dmesg | grep -i oom 确认,加 Swap 后重试。
Q:nginx 报 502 Bad Gateway? 应用没起来或端口不对,先 curl http://127.0.0.1:3000 自测,再看 journalctl -u myapp -n 100 的日志。
Q:certbot 验证失败? 先确认 DNS 已解析到本机、80 端口在安全组已放行,且 nginx 里该域名的 server 块已生效。
小结
部署的本质是一条链:域名解析 → 服务器环境 → 代码上传 → 进程常驻 → 反向代理 → HTTPS。每一步都有明确的验证手段,哪环断了查哪环。把这篇文章的命令存成自己的发版脚本,之后每次上线就是几分钟的事。
