jq现在已经比较少出现在新项目的技术架构中了,但一些技术革新比较慢的团队依然采用jq在开发服务,那如何针对jq做服务器渲染呢,这里介绍一种koa2+puppeteer的解决方案。
正好最近有个PHP+jq的项目需要做服务器渲染来做SEO(PS:不想重构架构,先以最简单的方案解决问题),系统架构如下图所示:
在不更改原有代码结构的基础上最简单的方法就是加一层中间层来做这事,如下图所示:
- (1)通过nginx将搜索引擎相关浏览引流到node服务器上;
- (2)启动koa2作为web服务器接收请求,对Url进行重定向,发给Puppetter;
- (3)puppeteer通过浏览器内核对页面的dom树进行渲染,返回给用户;
浏览器和服务端渲染流程差异对比
浏览器渲染主要会执行大量请求获取数据,而这是很耗时的,服务器渲染是指将数据获取在服务端拼接好直接发给浏览器,浏览器只要加载页面的数据就可以。
puppeteer 安装
puppeteer 在服务器安装其实还是挺多坑的,详细可以见:
https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix
这里简单介绍下puppeteer在服务器安装:npm i puppeteer
你以为这样就可以啦?其实大部分时候你运行将遇到崩溃,为啥?在安装目录下运行ldd
检查依赖就知道:$ cd ./node_modules/puppeteer/.local-chromium/linux-782078/chrome-linux/
$ ldd chrome | grep not
libatk-1.0.so.0 => not found
libatk-bridge-2.0.so.0 => not found
libXcomposite.so.1 => not found
libXcursor.so.1 => not found
libXdamage.so.1 => not found
libXfixes.so.3 => not found
libXi.so.6 => not found
libXtst.so.6 => not found
libcups.so.2 => not found
libgbm.so.1 => not found
libpangocairo-1.0.so.0 => not found
libpango-1.0.so.0 => not found
libcairo.so.2 => not found
libatspi.so.0 => not found
libXss.so.1 => not found
libgtk-3.so.0 => not found
libgdk-3.so.0 => not found
libgdk_pixbuf-2.0.so.0 => not found
一堆依赖库没装,毕竟大部分服务器环境不需要这些浏览器相关的动态库依赖。
Centos依赖包如下:alsa-lib.x86_64
atk.x86_64
cups-libs.x86_64
gtk3.x86_64
ipa-gothic-fonts
libXcomposite.x86_64
libXcursor.x86_64
libXdamage.x86_64
libXext.x86_64
libXi.x86_64
libXrandr.x86_64
libXScrnSaver.x86_64
libXtst.x86_64
pango.x86_64
xorg-x11-fonts-100dpi
xorg-x11-fonts-75dpi
xorg-x11-fonts-cyrillic
xorg-x11-fonts-misc
xorg-x11-fonts-Type1
xorg-x11-utils
一个个安装好就可以了。有些服务器kernel不支持 sandbox 模式,可以设置关闭 sandbox 模式:const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
puppeteer使用案例
// 导入包 |
chrome命令参数:https://peter.sh/experiments/chromium-command-line-switches/
可以用于chrome启动项的优化,比如将Dom解析和渲染放到同一进程、禁止初始化的默认url、禁止GPU等。
koa2 使用案例
koa2 是一个js的web服务器,安装很简单,只要node版本大于7.6.0即可,安装koa:$ npm i koa
下面是个简单koa的demo:const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
console.log(ctx.url);
ctx.body = 'Hello World';
});
console.log("running");
app.listen(10311);
测试运行:$ curl -i 127.0.0.1:10311
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 11
Date: Sat, 12 Sep 2020 20:05:45 GMT
Connection: keep-alive
hello world
这里我们主要用到 koa context,ctx 封装了 request 和 response 对象,详细参考:https://koa.bootcss.com/#context
完整例子
下面将 koa 和 puppeteer 结合做一个 SSR 渲染服务器:// 导入包
const puppeteer = require('puppeteer');
const Koa = require('koa');
const app = new Koa();
app.use(async ctx =>{
console.log(ctx.url);
let url = 'https://developer.orbbec.com.cn' + ctx.url
console.log(ctx.url);
// 因为服务器内核不支持sandbox,所以只能启用--no-sandbox
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
let time1 = new Date().getTime();
await page.setJavaScriptEnabled(true);
// 由于只关心渲染后的dom树,所以对css,font,image等都做了屏蔽
await page.setRequestInterception(true);
page.on('request', (req) => {
if(req.resourceType() == 'stylesheet' || req.resourceType() == 'font' || req.resourceType() == 'image'){
req.abort();
}
else {
req.continue();
}
});
// waitUntil 主要包括四个值,'load','domcontentloaded','networkidle2','networkidle0'
// 分别表示在xx之后才确定为跳转完成
// load - 页面的load事件触发时
// domcontentloaded - 页面的 DOMContentLoaded 事件触发时
// networkidle2 - 只有2个网络连接时触发(至少500毫秒后)
// networkidle0 - 不再有网络连接时触发(至少500毫秒后)
await page.goto(url, { waitUntil: ['load','domcontentloaded','networkidle2'] });
ctx.body = await page.content();
let time2 = new Date().getTime();
console.log((time2-time1)/1000)
console.log("finish");
// 关闭浏览器
await browser.close();
});
app.listen(10133);
优化
// 导入包 |