假设我们的项目做完了,现在该部署上线了。你有两个方向可以选择:
因为网络原因,国内无法直接访问 Vercel 。假如你要部署到自己的服务器,你有三种选择:
其中使用 Node.js Server,也就是我们目前开发的这种方式,主要步骤是将代码部署到服务器上,然后运行npm run start
启动 Node.js Server,适用于全栈项目。使用 Docker Image 的方式我们会在下篇文章讲到。静态导出适用于纯前端项目,在本地构建导出后,将构建的内容部署到服务器上。
本篇我们讲讲如何以 Node.js Server 的方式部署到自己的服务器上。我们先从买一个服务器开始说起。
现在我们买个服务器,这里我们以目前市场占有率第一的阿里云服务器为例,购买可以参考 《一篇从购买服务器到部署博客代码的详细教程》。
假设我们已经买完了,得到了一个全新的服务器:
注:当然你也可以重置一个服务器,此过程叫做“云服务器初始化”,是指将云服务器恢复到最初创建的状态,类似手机的恢复出厂设置。因为阿里云服务器数据是保存在云盘上的,所以这个过程也叫做重新初始化云盘,具体操作参考阿里云文档《重新初始化系统盘》。
首先要做的应该是重置密码。根据阿里云文档的《重置密码操作说明》,重置一下密码,否则我们无法登陆服务器。
获取密码后,我们尝试用命令行的方式登陆服务器:
# 语法:ssh 用户名@<实例的固定公网IP或EIP>
# 示例:
ssh root@39.100.83.124
# 输入实例登录密码
# 如果出现 Welcome to Alibaba Cloud Elastic Compute Service ! 表示成功连接到实例。
效果如下:
当重新登陆的时候,又要输入一次密码,为了能够免密码登陆,我们配置下公钥:
# 在本地起一个终端,获取本地公钥
cat ~/.ssh/id_rsa.pub
# 登陆服务器,将获取的公钥写入服务器的 authorized_keys
echo "这里修改为你的公钥内容" >> ~/.ssh/authorized_keys
这样我们再次登陆的时候就不需要输入密码了。注意,我们写入的是 ~
目录里,这就意味着如果我们切换了用户,是需要再按照这个方式重新配置一遍的。
登陆后如果我们一段时间没有操作,再操作的时候就会断开连接。为了长时间保持 SSH 会话连接不断开,需要设置下心跳以保持连接,运行:
vim /etc/ssh/sshd_config
添加配置项:
ClientAliveInterval 600
ClientAliveCountMax 10
TCPKeepAlive yes
ClientAliveInterval 600
表示 每 600 秒发送一次请求, 从而保持连接。ClientAliveCountMax 10
表示服务器发出请求后客户端没有响应的次数达到 10 次,就自动断开连接。所以无响应的 SSH 客户端将在大约 600 x 10 = 6000 秒后断开连接。
重启 sshd 服务,使配置生效:
systemctl restart sshd
各种环境(Nginx、Redis、MySQL 等)慢慢装其实也可以,但这里为了简单起见,我们直接装个宝塔。宝塔是一款简单好用的 Linux/Windows 服务器运维管理面板。使用宝塔,我们可以快捷的安装环境、查看文件、修改配置等。
登录服务器后,运行脚本:
if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
这里我们用的是万能安装脚本,适用于不确定使用哪个 Linux 系统版本的情况。其他脚本内容请参考宝塔的安装说明。
安装的时候需要输入一个 y
进行确认:
然后就是等待安装,大概 1 到 2 分钟左右会安装完。安装成功后,你会看到:
及时保存这些信息。 出于安全考虑,宝塔使用的端口号是随机生成的端口号,从上图中可以看出,这次端口号开在了 14913,为了能够正常访问,需要在云服务器 ECS 的安全组中开启此端口号。
打开服务器控制台:
添加此端口号,我们顺便把后面会用到的 3000
(项目开启在 3000 端口)、80
(HTTP 默认端口)、443
(HTTPS 默认端口)顺便也都开启了:
然后打开宝塔给出的外网面板地址,因默认启用自签证书 https 加密访问,浏览器将提示不安全,点击【高级】-【继续访问】或【接受风险并继续】访问。
然后输入命令行中给出的用户名和密码。第一次登录还需要勾选同意协议,并绑定宝塔账号。绑定后,会进入到宝塔的主界面:
首次登录宝塔的时候,宝塔会给出推荐安装套件提示:
当然你也可以在软件商店中一一安装:
这里我们至少需要把 Nginx 和 MySQL 安装了,至于 FTP、PHP、phpMyAdmin 则视个人情况选择。(我是直接在推荐的时候把 LNMP 都极速安装了。虽说是极速,安装还是需要一会的……)
然后安装 Node 环境。点击宝塔左侧的网站选项,打开 Node 项目,安装 Node 版本管理器:
如果列表中给出的 Node.js 版本比较老,你可以点击右侧的“更新版本列表”,选择一个合适的版本进行安装,这里我选择的是最新的稳定版 v20.11.0,选择后等待安装成功(安装的时候也会自动安装 PM2 模块):
注意安装完成后,图中的 “命令行版本” 要选择上,如果不选择的话,我们登录服务器,不会有 node 命令,也就是运行 node 相关的命令会报错。
如果你安装了 FTP,可以使用 FileZilla 这个 FTP 软件查看、编辑、上传、下载文件。Finder 虽然也可以连接 FTP 服务器,但无法上传、编辑、删除文件,功能有限。使用 FTP,首先要做一番配置:
打开服务器控制台-安全组:
添加 20
、21
和 39000-40000
范围端口:
在宝塔的 “安全” 面板里也把这些端口添加一遍:
注:阿里云服务器的端口范围写法为 39000/40000
,宝塔里的端口范围写法为 39000-40000
,略有不同。
然后在 “软件商店” 中修改 PureFTPd 的设置:
在“配置修改”里,搜索 ForcePassiveIP
,然后将其值更改为服务器的 IP:
回到宝塔的“FTP”界面,点击“添加 FTP”,添加一个用户名和密码,根目录设置为 /www/wwwroot
,这是宝塔的默认建站目录,我们也会把项目的代码放到这个目录下。
添加用户后,点击查看快速连接,根据提示使用 FTP 工具连接即可:
成功连接后的效果如下:
现在我们买个域名,购买域名可以参考《一篇域名从购买到备案到解析的详细教程》。
假设我们已经购买并备案好了。在阿里云域名控制台可以查到:
现在我们把域名解析到我们的服务器地址上。点击左边的“解析”按钮,然后点击“添加记录”:
这里我们用的是二级域名 notes.yayujs.com
,记录值填写我们购买的服务器地址。添加后,应该很快就会生效。
阿里云提供免费的 SSL 证书(很多网站都提供的),最近证书的有效期从之前的 1 年改为了目前的 3 个月,虽然有些麻烦,但也能凑合着用。
参考《VuePress 博客优化之开启 HTTPS》,搞一份免费证书:
点击“下载”,服务器类型选择 nginx,点击“下载”,会下载证书文件,包含一个 .pem
文件,一个 .key
文件。
这两个文件留着备用,后面开启 HTTPS 会用到。
现在我们尝试部署我们的项目,首先要保证自己的项目本身没有问题。所以我们在本地先 npm run build
,然后 npm run start
检查一下运行。为了简单起见,我们可以在脚本里再添加一个 prod
命令,修改 package.json
:
{
"name": "next-react-notes-demo",
"version": "0.1.0",
"private": true,
"scripts": {
// ...
"prod": "next build && next start"
},
"dependencies": {
// ...
},
"devDependencies": {
// ...
}
}
当运行 npm run prod
的时候,就会先构建后运行,省的每次都要手动执行两个命令。
现在我们在本地运行生产版本,发现新建笔记的时候会报错:
在生产版本中,next-auth
并不认为 localhost:3000
是值得信任的 Host,使用 next-auth
正常部署网站时,需要将 NEXTAUTH_URL
环境变量设置为网站的规范 URL:
NEXTAUTH_URL=https://example.com
如果开发的时候确实没有正式的地址,可以在 auth.js
中强行设置 trustHost
。修改 auth.js
,添加 trustHost: true
配置项:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import CredentialsProvider from "next-auth/providers/credentials";
import { addUser, getUser } from "@/lib/prisma";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers:[// ... ],
pages: {
signIn: '/auth/signin'
},
callbacks: {
// ...
},
trustHost: true
})
这样再运行 npm run prod
的时候,就不会有 auth 报错了。不过我们既然已经有了域名,可以在根目录新建一个 .env.production
文件,设置生产环境的 NEXTAUTH_URL
:
AUTH_URL=https://notes.yayujs.com
这样部署线上的时候就不会报 untrustedHost 错误了。
注:next-auth v5 里,AUTH_URL
是 NEXTAUTH_URL
的别名。
本地运行没啥问题,我们就可以把项目代码提交到服务器上。虽然可以连接服务器直接上传代码,但为了以后每次提交方便,我们在服务器上创建一个裸仓,这样每次更新代码,Git 提交到远程,服务器上的代码就会自动更新,这样使用起来更加方便。
登陆服务器,运行 yum install git
,安装 Git,效果如下:
安装后,运行:
# 1. 进入目录,这是宝塔的默认建站目录
cd /www/wwwroot
# 2. 建个仓库文件夹
mkdir notes.git && cd notes.git
# 3. 创建裸仓库
git init --bare .
这个时候我们就获得了一个服务器上的仓库地址:
root@39.100.83.124:/www/wwwroot/notes.git
这里我们使用 git init --bare
初始化仓库,它与我们常使用的 git init
初始化的仓库不一样,你可以理解为它专门用来创建远程仓库,这种仓库只包括 git 版本控制相关的文件,不含项目源文件,所以我们需要借助一个 hooks,在有代码提交到该仓库的时候,将提交的代码迁移到其他目录,这里我们在 notes.git
同级目录下创建了一个 notes
文件夹,用于存放提交的源代码文件:
# 1. 进入 hooks 目录
cd hooks
# 2. 创建并编辑 post-receive 文件
vim post-receive
# 3. 这里是 post-receive 写入的内容,注意根据自己的实际情况修改
#!/bin/bash
git --work-tree=/www/wwwroot/notes checkout -f
# 4. 赋予执行权限
chmod +x post-receive
# 5. 退出目录到 notes.git 同级目录并创建项目目录
cd ../../ && mkdir notes
如果顺利的话,现在在本地打开我们的 React Notes 项目, commit
提交代码后,运行:
git push -f root@39.100.83.124:/www/wwwroot/notes.git master
以后每次修改代码、提交代码后,运行这行,代码就会自动更新到服务器上。
此时服务端的 notes
文件夹应该有我们提交的代码了,我们可以通过宝塔的“文件”查看:
目前服务器上的 MySQL 数据库还没有设置,我们直接在宝塔添加一个 MySQL 数据库:
这里我们创建了一个名为 notes
的数据库,用户名自动设置为 notes
,你不能更改为 root
。设置后你可以以 notes
用户加密码的方式访问这个数据库。这样我们写在 env
中 DATABASE_URL
就要修改为:
DATABASE_URL="mysql://notes:WKNSjB3DeNB4xYje@localhost:3306/notes"
当然,我们也可以改 root
密码,把 root
密码改为 admin
,这样连代码都不用改了……
当然处于安全的角度考虑,最好还是使用 notes 用户。项目根目录新建一个 .env.development
文件,代码如下:
DATABASE_URL="mysql://root:admin@localhost:3306/notes"
.env
中设置线上的 DATABASE_URL:
// ...
DATABASE_URL="mysql://notes:i54f8znxfy2wh3w3@localhost:3306/notes"
还记得服务器代码修改的流程吗?本地代码修改后,正常运行 git add
、git commit
命令后,运行:
git push -f root@39.100.83.124:/www/wwwroot/notes.git master
服务器代码就会发生改变,你可以通过宝塔的“文件”面板或者 FTP 工具查看服务器上的文件。
因为我们代码中还使用了 Prisma,目前 MySQL 数据库只是新建了数据库,但没有同步数据模型,所以我们登录服务器进入项目目录:
# 1. 进入项目目录
cd /www/wwwroot/notes
# 2. 安装依赖
npm install
如果 npm install
有网络问题,可以设置 npm 镜像:
# 设置成 npm 镜像
npm config set registry https://registry.npmmirror.com
# 改回来
npm config set registry https://registry.npmjs.org/
这个时候我们执行 prisma 相关的命令,我们先运行 npx prisma -v
试试,大概率会出现这个错:
如果出现这个错误,运行:
# 1. 编辑环境变量
vim ~/.bashrc
# 2. 添加环境变量
export PRISMA_ENGINES_MIRROR=https://registry.npmmirror.com/-/binary/prisma
# 3. 保存退出后运行
source ~/.bashrc
然后再次运行 npx prisma -v
试试:
这说明 prisma 已经可以正常使用了。此时我们的数据模型要同步数据库。
如果你把迁移时生成的 prisma/migrations
下的文件也都提交了,那你可以在服务器上运行:
npx prisma migrate deploy
正常部署效果如下:
然后运行 npx prisma generate
初始化 Prisma Client:
npx prisma generate
如果你没有提交,反正也是初始化,你可以运行:
npx prisma migrate dev
此时通过 Prisma,MySQL 的数据表也设置完毕。
现在我们运行 npm run prod
试试,应该能顺利的运行在 3000 端口:
然后这个时候访问 http://39.100.83.124:3000/
可能还打不开,因为 3000 端口还没有开启,先在宝塔的“安全”面板上开启:
我们之前只是在阿里云 ECS 的安全组中开启了 3000
(项目开启在 3000 端口)、80
(HTTP 默认端口)、443
(HTTPS 默认端口),这里需要在宝塔里也开启一遍。
此时打开 http://39.100.83.124:3000/
应该能正常访问了:
不过点击 Sign In
的时候还会报错,不着急,我们慢慢解决,先学习点基础知识。
现在我们先在服务器上关掉项目的 node 程序。如果 ssh 还在保持连接,会话还在,那直接 control + c
也就断开了。但如果 ssh 断开了连接,恢复之前的会话就有些麻烦了。搞不好还要先强行关闭 3000 端口:
# 1. 获取端口进程 PID
lsof -i:3000
# 2. 关闭 PID
kill -9 xxxx
这个时候就要说到 Screen 了。Screen 是 Linux 下的一个多重视窗管理程序。拥有多窗口、会话恢复、会话共享等功能。所谓会话恢复,对于远程登录的用户,即使连接中断,用户也不会失去对已经打开的命令行会话的控制。只用登录到服务器,运行 screen -r
即可恢复会话。
登陆服务器,安装 Screen:
yum install screen
Screen 的命令并不多,简单介绍下常用到的:
Screen 的状态分为两种:Attached(前台运行) 和 Detached(后台运行)。
通过 screen -S xxx
可以新建一个会话,并进入 Attached 状态。如果该程序要长期运行,但你要暂时离开该会话,Control + A Control + D
,该会话就会进入 Detached 状态。当你再登陆进来的时候,可以通过 screen -ls
查看有哪些会话,然后通过 screen -r xxxx
恢复会话,将会话从 Detached 状态转为 Attached 状态。
如果你要进入 Attached 的会话,你需要先通过 screen -d xxxx
,将其状态转为 Detached
,才能通过 screen -r xxxx
进入会话。这个过程也可以合并成 screen -d -r xxxx
一个命令。
如果你要删除某个 screen,运行 screen -S xxxx -X quit
。
PM2 是部署 node 项目常用的一个工具。正常我们要安装 pm2 这个模块,但我们在宝塔装 Node.js 版本管理器的时候,宝塔已经为我们安装了 PM2 模块,所以登陆服务器,直接就有 pm2 命令了,不信你运行 pm2 -v
试试。
PM2 是一个 Node.js 进程管理器,使用 PM2,可以实现性能监控、自动重启、负载均衡等。让我们看下常用的命令吧:
# 启动
pm2 start app.js
# 声明一个名字
pm2 start app.js --name app_name
# 监听文件变化
pm2 start python-app.py --watch
# 列表
pm2 list
# 监控
pm2 monit
# 重载
pm2 reload app_name
# 重启
pm2 restart app_name
# 停止
pm2 stop app_name
# 删除
pm2 delete app_name
我们来看这样一个命令:
pm2 start npm --watch --name notes -- run prod
这个命令的意思是以 pm2 开启 npm run prod
脚本命令,此进程命名为 notes,并监听文件变化。
现在回到我们的项目上。在宝塔的“网站”面板中添加 Node 项目,相关配置如图:
添加成功后:
此时宝塔已经为我们的项目做好了 Nginx 配置,可以在设置中查看:
宝塔做的 Nginx 配置为监听 notes.yayujs.com
的 80
端口,然后代理到 http://127.0.0.1:3000
。
我们在 Screen 中运行我们的 Next.js 项目吧:
# 1. 登陆服务器
screen -S notes
# 2. 进入程序目录
cd /www/wwwroot/notes
# 3. 运行脚本
pm2 start npm --watch --name notes -- run prod
稍等一会,等待编译构建完成,我们再访问 http://notes.yayujs.com/
应该可以正常运行了:
但是当我们点击 Sign In
的时候,依然会报错。服务器上的报错为:
之所以会有这个报错,是因为我们的 Nginx 配置中 x-forwarded-host
标头带了端口号,在宝塔上修改 Nginx 配置,把下图中标示的 $server_port
去除即可。
Ctrl+S
保存后,Nginx 配置就会自动生效。此时再访问 http://notes.yayujs.com/
,点击 Sign In
,虽然还是报错,但至少不报刚才那个错了……哈哈哈哈
现在让我么来解决 HTTPS 的问题吧。点击 Node 项目的“未部署”按钮:
还记得之前在阿里云服务器上下载的
.key
和 .pem
文件吗?用编辑器打开这两个文件,将这两个文件中的内容复制到这两个输入框中,然后点击“保存并启用证书”。宝塔会自动更新 Nginx 配置:
可以看到,Nginx 配置更新,监听 HTTPS 的 443 端口,代理到 3000 端口。
此时访问 https://notes.yayujs.com/
,依然可以正常访问,不仅如此,点击 "Sign In"
也能正常登录了,项目运行正常:
此外,因为我们使用的是 pm2 运行的项目,并设置了监听文件变化,所以当你通过 git push -f root@39.100.83.124:/www/wwwroot/notes.git master
推送代码到服务器的时候,因为文件变化,pm2 会自动重启该应用。
关于宝塔的 Nginx,我们多说一点,了解就行,以防万一用到。
正常我们登陆服务器直接安装 Nginx,比如参考《一篇从购买服务器到部署博客代码的详细教程》中的方式,Nginx 的配置文件是放在 /etc/nginx
目录下的。但是宝塔装的 Nginx 并没有放在该目录下。
此外,因为宝塔面板内有些包是未托管到服务器 systemd 包管理内的,所以 systemctl 是无法正常使用的。也就是说,使用 systemctl status nginx.service
这种命令是无法准确判断 nginx 状态的,当然也无法重启重载等。
宝塔的 Nginx 安装在了 /www/server/nginx
,配置文件在 /www/server/nginx/conf/nginx.conf
,其内容可以通过目录直接查找,也可以在 Nginx 的设置中查看:
这是 nginx 的主配置文件,在这个文件中底部有一句:
include /www/server/panel/vhost/nginx/*.conf;
打开此目录:
这其实就是我们具体项目的 Nginx 配置,可以在具体项目的设置里查看:
如果你要操作宝塔的 Nginx 配置,可以在命令行中运行:
# 启动
/etc/init.d/nginx start
# 停止
/etc/init.d/nginx stop
# 重启
/etc/init.d/nginx restart
# 重载
/etc/init.d/nginx reload