服务端渲染(ssr)项⽬及nuxt项⽬部署过程
导⾔
去年有写⼀篇的博客,其中有提到ssr项⽬部署问题,关于这个实在是可讲的太多,因此单独写了⼀篇,就是本⽂。
csr与ssr部署
传统的客户端渲染(csr)项⽬的部署,即是把webpack打包后⽣成的静态⽂件(dist)上传到服务器上,通过配置⽹关及nginx转发,使外⽹客户端可以访问到这些html⽂件。
⽽服务端渲染(ssr)项⽬,依赖web服务器动态构建html⽂件,因此之前的csr⽅法肯定不得⾏。从这个思路出发,ssr项⽬需要在服务器在将web服务器(web server)跑起来,并且使外⽹客户端可以访问到web服务器(这⾥与csr部署思路⼀致,⽅法略有不同),再根据不同的条件⽣成不同的⾸屏html并返回给客户端。
本⽂主要讲两种部署⽅法:基础ssr的部署⽅法(按照vue官⽹的ssr指南构建的项⽬)和使⽤nuxt.js框架的部署⽅法。
ssr部署⽅法
以下使⽤到的命令建议梳理记录,后续可以将这些命令列成⼀个启动脚本,实现⾃动化启动。
【p1】web server的部署
想要将web server部署在服务器上,服务器上必须要有node.js环境,才能在web server跑起来之后提供⼀个node上下⽂环境,只是硬性要求。测试⽅法很简单,node命令有效即可,如下:
node -v
如果没有,就需要在服务器上安装⼀下node.js了。
然后需要将对应的server启动⽂件(server.js)上传到服务器上,这⾥⼤家应该都有⾃家的⽂件上传服务器的脚本,不加赘述。
在服务器上试着跑⼀下web服务器
node ./server.js
发现报错,会提⽰module not found,原因是server.js⽂件⾥可能依赖(require)了其他依赖项,它在本地不到这些依赖项,⼀些可能是node.js⾃⾝的⼀些模块,⽐如fs,path这种,这些依赖并不需要额外引⼊,因为node环境中已经包含,另⼀些就是外部的js库,⽐如koa.js这类web server框架,需要额外引⼊,该怎么办呢?
// server.js头部引⽤
let fs = require('fs');  // node模块
const path = require('path');  // node模块
const Koa = require('koa');  // 外部依赖
const Router = require('koa-router');  //外部依赖
答案就是需要把依赖项也同时上传到服务器上,这⾥有两种⽅法:⼀种是直接将依赖下载到项⽬内,并且通过脚本与server.js⽂件⼀同上传⾄服务器上;另⼀种是将这些需要额外引⼊的依赖清单列成⼀个package.json,跑server.js之前先通过npm install把这些依赖下载下来。
这些依赖需要存放到node_modules⽂件夹中,并且按照⽂件夹层级逐级索引,当本⽂件夹内不存在时
则向上⼀级⽂件夹索引,⽽且由于我们的服务器依赖基本上是很少改变的,可以做到不⽤每次都下载这些体积不算⼩的依赖,具体的处理⽅法因⼈⽽异,这⾥只是提出⼀点思路,希望这点⼩细节可以帮助你⽅便处理服务器的依赖项。
当依赖下载完成,再次尝试跑起来web服务器,检查web服务器运⾏的端⼝,如果可以响应,这就表⽰我们完成了第⼀步。
# 就绪检查命令可以这么写
curl host:port/你的检查服务器的路由
# 例如我的server.js是这么写的:
// 就绪检查
<('/heart-beat', ctx => {
});
const host = v.HOST || '0.0.0.0';
const port = v.PORT || 8090;
app.listen(port, host);
console.log(`Server listening on ${host}:${port}, now is ${new Date().toLocaleString()}`);
# 那么检查就绪的命令就可以这么写
curl 127.0.0.1:8090/heart-beat
# web server响应:
'ok'
# 这就表⽰完成了
【p2】静态资源⽂件的上传
静态资源⽂件即是指webpack打包后⽣成的⽂件,⽆论是server bundle还是client bundle都应该和server.js⼀起上传到服务器上,在通过web服务器按条件进⾏处理,返回给客户端。
在⽣成server bundle和client bundle⽅⾯,这⾥详述起来会⽐较复杂,以后会再起⼀篇⽂章,讲⼀下按照vue官⽅的ssr指南如何构建⼀个ssr项⽬。
server bundle交付给web服务器,服务器在处理拼接html时,可能也会⽤到⼀些外部依赖,⽐如vue、axios这类。这些很容易理解:因为它实际上是将你的bundle⽂件执⾏了⼀遍,执⾏过程中遇到引⽤的其他依赖,当然需要在当前的环境也有这些依赖。
这⾥总结⼀下ssr项⽬需要在服务器上⽤到的依赖:
1、server.js require的依赖(node模块除外)
2、src内源码⽂件在头部import的依赖
3、src内院吗⽂件在头部import的项⽬⽂件内⽤到的依赖(⽐如你引⽤了⾃⼰的util/tool.js,在tool.js内import的依赖也需要安装)
当然这些依赖都不会超出你的package.json内依赖的范围,如果你觉得⿇烦,也可以直接将package.json内的依赖全部安装到服务器上。
静态资源⽂件上传完毕以后,就可以按照上⾯检查web server就绪的⽅法,检查是否可以完成html⽂件的组装
curl host:port/你的ssr路由
如果web服务器响应并且返回⼀个html⽂件,那么恭喜你已经完成了80%的⼯作。
【p3】node进程的维护管理
完成以上两步,基本上表⽰你的项⽬可以正常运⾏,还差最后⼀步:如何维护你的node进程正常运⾏,假设遇到了意外问题,服务崩溃了,如何维护web服务器重启?pm2应运⽽⽣。
在你的服务器上安装pm2,参考
pm2是⼀个⽤于维护node进程的管理⼯具,通过配置⼀个简单的⽂件,即可帮你维护你的node进程,并且可以做到地址端⼝复⽤,多实例分流,异常重启等,⼗分强⼤。配置⽂件如下:
// fig.js
apps: [{
name: 'my-ssr-app',
script: './build/server.js',  // 你的web server⼊⼝⽂件
args: 'start',
// 应⽤启动的路径
cwd: './',
// 应⽤启动模式,⽀持fork和cluster,cluster⽀持地址端⼝复⽤
exec_mode: 'fork',
// 应⽤启动实例个数
// fork模式下不能起多个实例会报错
instances: 1,
// instances: "max",
// 最⼤内存限制数,超出⾃动重启
max_memory_restart: '1G',
// 监听重启,⽂件夹变化⾃动重启
watch: ['dist', 'build'],
// 应⽤运⾏少于该时间认为启动异常
min_uptime: '3s',
// 发⽣异常的情况下⾃动重启
autorestart: true,
// 最⼤异常重启次数,即⼩于min_uptime运⾏时间重启次数
max_restarts: 3,
// 异常重启情况下,延时重启时间
restart_delay: 3000,
error_file: './pm2-log/err.log',
out_file: './pm2-log/out.log',
combine_logs: true,
env_production: {
'NODE_ENV': 'production'
},
env_release: {
'NODE_ENV': 'release'
}
}]
};
配置完成后,需要通过pm2启动你的web服务器:
// package.json的启动命令
"scripts": {
  "pm2:release": "pm2 fig.js --name my-ssr-app --env release"
}
在通过pm2命令检查你的服务器是否已经启动完成
pm2 ls
常⽤的pm2命令,如重启实例、删除实例等你可以在⽹上查询资料。
⼀些常见问题:
1、cluster模式EBIG问题
关于使⽤pm2的cluster模式会创建多个实例,⾃动实现负载均衡,⽬前仅⽀持node,相较于fork模式的单实例稳定。
实现cluster模式会复制当前node的环境变量,如果环境变量多于⼀定值,会报EBIG错误,⽬前可借鉴的解决⽅案如下:
(1)每次pm2运⾏之前清除当前环境的环境变量(可能影响服务器内其他服务,毕竟服务器上⼀般都不⽌⼀个服务)
(2)修改pm2代码,把⽆⽤环境变量过滤掉。此⽅案需要维护⼀个私有的pm2包
(3)不使⽤cluster模式,使⽤fork模式(我现在选⽤的⽅案)
参考资料:
2、部分permission denied问题
请注意你登录服务器的⽤户⾝份,最好是管理员,如果不是,可能会导致⼀些功能异常。
3、⽇志问题
另外由于pm2会将项⽬内在node运⾏期间的⽇志(console.log)记录下来,⽣成⽇志,⽇志⽂件保存在配置⽂件内指定的地⽅,如果长期没有清理这些⽇志,则会累积导致内存或者磁盘占⽤。当然现在的服务器环境在每次启动时都会删除之前的⽂件夹⽣成新的⽂件夹,⽇志⽂件也会随之清理。但是如果长期没有重启也会有这个问题,所以建议使⽤pm2的⼀个⽇志管理模块pm2-logrotate,你可以在⽹上查询到如何配置、使⽤它。
4、环境变量
⽐如说运⾏的环境变量(NODE_ENV),这是⼀个很重要的变量,⽤于区分当前运⾏环境是开发、测试还是线上环境,这个变量不再由node直接注⼊,⽽是pm2,这就牵扯到配置⽂件内,配置环境的问题了:
env_release: {
'NODE_ENV': 'release'
}
像这样在配置⽂件内规定了env_release环境内注⼊的NODE_ENV,再在启动命令加上--env release,pm2就知道我们需要注⼊什么环境变量了。
【p4】nginx配置
以上均配置完成后,现在要做的就是要配置nginx让外⽹客户端可以访问到我们的web服务器。
⽹关分流完成后,所有对应的流量都会导向到我们服务器上。
配置nginx,将符合条件的访问都导向到我们的web服务器上:
location / {
proxy_pass 127.0.0.1:web服务器运⾏的端⼝/;
}
修改server.js,保证path⼀致:
// 最后访问地址:你的外⽹host/⽹关标识/实际访问路由
<(('/⽹关标识/') + 实际访问路由, ctx => {
//
})
在浏览器上访问你的期望的url,现在就可以正常访问到你的ssr页⾯了。
【p5】总结
1、上传到服务器上的⽂件清单
dist :打包后的bundle⽂件
server.js :web服务器的启动⽂件
package.json :需要⽤到⾥⾯的依赖项列表&部分启动命令也可以存在这个⾥⾯
node_modules :可以⼿动上传到服务器上,也可以在通过package.json安装之后⽣成
⼀些其他脚本⽂件及server.js⾥引⽤的js⽂件
2、启动命令
所有需要⽤的命令可以整理在⼀个sh⽂件(start.sh)⾥,然后在Dockerfile⾥,完成所有配置后,启动它。⾄此就实现了⾃动启动了。
# Dockerfile
RUN chmod +x /opt/apps/my-ssr-app/start.sh
# 安装pm2⽇志管理模块
RUN pm2 install pm2-logrotate
CMD /opt/apps/my-ssr-app/start.sh
// start.sh
cd /opt/apps/my-ssr-app
npm run pm2:release
nuxt部署⽅法
nuxt基本帮我们把该做的都做了,服务器也准备好了,都打包在.nuxt⾥
1、准备必须依赖项
server/index.js下引⽤的
⼀般为modules或者build⾥提到的模块
2、修改fig.js
(1)buildDir:⽣成的构建⽂件夹名字,不建议使⽤默认名字,默认名.nuxt是个隐藏⽂件夹,这会导致在镜像中复制⽂件时丢⽂件夹。命名建议不以符号开头,不与路由重复。
(2)env:环境变量,nuxt中环境变量(v.NODE_ENV)只有两个固定值(development,production)且不能新增值,如需要⾃定义别的值,需要在这⾥声明。(3)build.publicPath:对资源⽂件的索引路径,需要与服务器或者CDN上资源⽂件路径⼀致,建议使⽤绝对路径,避免出现问题
(4)router.base:应⽤的根URL,此路径必须和外⽹访问的基本路径⼀致,否则会导致nuxt⾃动跳转404页⾯。
举例:假设外⽹访问根路径为host/pathA/pathB/xxx,其中pathA为⽹关配置的应⽤区分路径,不可缺少;pathB为我们配置的nginx代理关键词,那么我们服务的router.base应为/pathA/pathB/,nginx的配置应为
location ^~ /pathB/ {
proxy_pass 127.0.0.1:port/pathA/pathB/;
}
nginx部署前端项目
3、修改fig.js
(1)script:pm2启动的⼊⼝,⼀般是服务器⽂件,server/index.js,注意这两个⽂件的路径需要做好对应
(2)cwd:项⽬启动的路径,⼀般为当前路径
(3)exec_mode:如果解决了cluster下的EBIG的问题,cluster较好
(4)instances:pm2创建的实例数量,如果使⽤fork,只能为1,否则需要做负载均衡;使⽤cluster,则可以为"max"
4、准备需要上传到服务器的⽂件,必须⽂件如下:
(1)fig.js:nuxt配置⽂件
(2)fig.js:pm2配置⽂件
(3)package.json:⼊⼝⽂件
(4)server/:服务器⼊⼝⽂件,pm2的script配置的启动⼊⼝即此⽂件
(5).nuxt:nuxt build⽣成的⽂件夹,与fig.js中定义的buildDir⼀致
(6)其余为fig.js⾥引⽤到的项⽬内⽂件
5、关于上传到cdn上的⽂件:
需要上传到cdn的是nuxt构建的nuxt-dist/dist/client/下的部分,上传到cdn之后,需要修改fig.js⾥的build.publicPath,对应此⽬录在cdn的路径

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。