rendertron 是 Google 团队的一个对网页进行渲染的项目
它利用 headless chromium
对指定的网页进行实时渲染
vue / react
这类框架的兴起,给前端开发带来了巨大的变化
然而默认他们都是使用的 csr
这带来一个问题,由于 spa 应用 中绝大部分的 html dom
都是由 js
去控制产生销毁的
我们浏览器访问一个这样的应用,去查看源代码的话
只能看到引入了大批的 js 文件,并没有任何显示的数据
如图:
一些 老爬虫 的视角也是如此,他并不会执行其中的 js
也就没法通过里面的 xhr
等等异步方式去获取数据,给 body
里面添加内容
所以以它们的视角来看,我们的 spa
应用简直可以用空无一物来形容
这就对我们做 seo
带来了问题
预渲染技术也是使用 puppeteer
,在我们 spa
项目打包完成的阶段,预先 host 这些静态文件,然后使用 headless chrome 先去渲染它指定的页面,把渲染好的 html 抽离,和 spa 项目一起按照目录的结构部署
这种方案最大的优点是非常的方便 , 小项目使用非常的灵活
不过,假如说有一个动态页面,要根据百万量级数据去生成百万个静态的 html
这种方案显然是无法接受的,毕竟渲染这么大的量很花时间,部署的话,这么多静态的东西占用空间也很大,得不偿失
之前我选用的就是 ssr
的解决方案
vue / react
项目都有对应成熟框架
比如 nuxt/next/umi
, ssr
方案能解决一些问题,
比如说首屏渲染问题,还有 seo
的一些 meta
信息,也可以在服务端组装好返回
而且对前端开发来说,由于 nodejs
扮演角色的升级,能做的事情也更多了
不过可能也存在一些问题:
-
对已经开发好的
spa
项目,有可能改造成本比较高 -
对前端开发人员,有更高的技术要求,要清楚哪些代码跑在
nodejs
,哪些在brower
,而哪些二者都在跑
然而他的优点也是显而易见的 , 推荐大家采用这种方案去搞 seo
rendertron 这种方案,给我们在 spa 应用无法改造时候多一条出路
他可以在尽可能少改造原先的项目的情况下,达成我们 seo 的部分目的
原理实际上也非常简单:
通过使用 Headless Chrome
在内存中执行 Javascript
,并在得到完整内容后,将内容返回给客户端。
rendertron
本身只是一个 puppeteer
和 koa
的封装, 可以作为一个koa
应用直接部署。
在阿里云 serverless
,通过 fun
的配置项,可以直接部署一个 rendertron
应用
而腾讯云 serverless
, 也已经内置了 chromium
, 我们也只需要安装 puppeteer
,不需要上传任何的二进制文件,就能直接使用了
目前腾讯云 SCF 内置版本是 HeadlessChrome/80.0.3987.0 (2021.04.19)
本文章以腾讯云 serverless 作为示例,毕竟直接部署一个现成的 rendertron
太无趣了,可操作的空间不多
创建一个任意的 spa
应用,比如 vite
+ vue3
, 随便写一点 xhr
, 异步的渲染, 作为 demo
创建一个 express
应用,用 history-api-fallback
去处理 history
路由的 spa 应用的 index.html
安装 rendertron
,我们不需要初始化 一个 Rendertron
实例,只借用他的 Renderer
中的策略
创建 Renderer
实例之后,我们就有了自己的 browser
和 renderer
了
然后简单写个 中间件 , 用 isBot
判断是不是爬虫
是爬虫就 去判断缓存,没有命中就把爬虫访问的 fullPath
的页面,用 Renderer
去实时渲染,然后返回给爬虫,并置缓存
是用户就正常返回 index.html
例如:
// 以cloudbase json db为例
const rendererMiddleware = async (req, res, next) => {
const ua = req.get('user-agent')
const botFlag = isbot(ua)
if (botFlag) {
const fullURL = req.protocol + '://' + req.get('host') + req.originalUrl
const { data } = await pageCacheCol
.where({
url: fullURL
})
.get()
if (data.length) {
const hit = data[0]
// 由于实时渲染,又慢内存消耗又大,建议使用永久缓存,通过检测xhr获得数据的变化,手动去刷新缓存
res.send(hit.content)
// 半小时的cache 方案(废弃)
// const ts = Date.now()
// if (ts - hit.ts <= 1000 * 60 * 30) {
// res.send(hit.content)
// } else {
// await rendererWithInsetDb()
// }
} else {
const result = await rendererMoudle.renderer.serialize(fullURL, true)
res.send(result.content)
await pageCacheCol.add({
url: fullURL,
// ts: Date.now(),
content: result.content
})
}
} else {
next()
}
}
这样,基于 SCF
的一个自定义的 rendertron
就完成了
效果可以直接上 https://rendertron.icebreaker.top/ 查看
这个方案对于我们的用户来说,体验的就是最纯正的 spa
应用,
像 ssr
应用,假如不做页面/组件/数据这方面的缓存的话,复杂页面的渲染实际上也是比较慢的。
而这种方案去实时渲染的话 , 计算负担相当于从 nodejs server side
的渲染引擎 那里,转移到了 chromium
这里
简直就是:
又慢 , 又非常的耗内存
假如项目改造成本不高,建议直接上 ssr
假如需要采用这种方案的话,也比较适合做永久缓存,然后根据页面数据的变化,在推送 job
中手动刷新指定路由的缓存,然后去更新指定的 sitemap
中的 lastmod
字段 ,再 主动推送 给搜索引擎
而使用缓存过期的策略,不如每天凌晨,用 job 全部手动渲染,刷一遍缓存要好。
不过页面或者数据量大的情况下,也比较花时间花内存。
得不偿失。