此页面需要javascript支持,请在浏览器中启用javascript

serverless 场景下的 rendertron 方案

rendertron
chromium
serverless
共1735个字,阅读时间 9 分钟
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://icebreaker.top/articles/2021/4/21-serverless-rendertron

什么是 rendertron ?

rendertron 是 Google 团队的一个对网页进行渲染的项目

它利用 headless chromium 对指定的网页进行实时渲染

为什么会出现这个项目?

vue / react 这类框架的兴起,给前端开发带来了巨大的变化

然而默认他们都是使用的 csr

这带来一个问题,由于 spa 应用 中绝大部分的 html dom 都是由 js 去控制产生销毁的

我们浏览器访问一个这样的应用,去查看源代码的话

只能看到引入了大批的 js 文件,并没有任何显示的数据

如图:

一些 老爬虫 的视角也是如此,他并不会执行其中的 js

也就没法通过里面的 xhr 等等异步方式去获取数据,给 body 里面添加内容

所以以它们的视角来看,我们的 spa 应用简直可以用空无一物来形容

这就对我们做 seo 带来了问题

解决问题的几种方案

prerender

预渲染技术也是使用 puppeteer,在我们 spa 项目打包完成的阶段,预先 host 这些静态文件,然后使用 headless chrome 先去渲染它指定的页面,把渲染好的 html 抽离,和 spa 项目一起按照目录的结构部署

这种方案最大的优点是非常的方便,小项目使用非常的灵活

不过,假如说有一个动态页面,要根据百万量级数据去生成百万个静态的 html

这种方案显然是无法接受的,毕竟渲染这么大的量很花时间,部署的话,这么多静态的东西占用空间也很大,得不偿失

server side render

之前我选用的就是 ssr 的解决方案

vue / react 项目都有对应成熟框架

比如 nuxt/next/umi , ssr 方案能解决一些问题, 比如说首屏渲染问题,还有 seo 的一些 meta 信息,也可以在服务端组装好返回 而且对前端开发来说,由于 nodejs 扮演角色的升级,能做的事情也更多了

不过可能也存在一些问题:

  • 对已经开发好的 spa 项目,有可能改造成本比较高

  • 对前端开发人员,有更高的技术要求,要清楚哪些代码跑在 nodejs,哪些在brower,而哪些二者都在跑

然而他的优点也是显而易见的,推荐大家采用这种方案去搞 seo

rendertron

rendertron 这种方案,给我们在 spa 应用无法改造时候多一条出路

他可以在尽可能少改造原先的项目的情况下,达成我们 seo 的部分目的

原理实际上也非常简单:

通过使用 Headless Chrome 在内存中执行 Javascript,并在得到完整内容后,将内容返回给客户端。

rendertron 的 serverless 实践

rendertron 本身只是一个 puppeteerkoa 的封装,可以作为一个koa应用直接部署。

在阿里云 serverless,通过 fun 的配置项,可以直接部署一个 rendertron 应用

而腾讯云 serverless, 也已经内置了 chromium , 我们也只需要安装 puppeteer ,不需要上传任何的二进制文件,就能直接使用了

目前腾讯云 SCF 内置版本是 HeadlessChrome/80.0.3987.0 (2021.04.19)

本文章以腾讯云 serverless 作为示例,毕竟直接部署一个现成的 rendertron 太无趣了,可操作的空间不多

Step1

创建一个任意的 spa 应用,比如 vite + vue3 , 随便写一点 xhr , 异步的渲染,作为 demo

Step2

创建一个 express 应用,用 history-api-fallback 去处理 history 路由的 spa 应用的 index.html

安装 rendertron ,我们不需要初始化 一个 Rendertron 实例,只借用他的 Renderer 中的策略

创建 Renderer 实例之后,我们就有了自己的 browserrenderer

然后简单写个 中间件 , 用 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 就完成了

效果展示

爬虫视角 (Googlebot)

效果可以直接上 https://rendertron.icebreaker.top/ 查看

总结

这个方案对于我们的用户来说,体验的就是最纯正的 spa 应用,

ssr 应用,假如不做页面 / 组件 / 数据这方面的缓存的话,复杂页面的渲染实际上也是比较慢的。

而这种方案去实时渲染的话,计算负担相当于从 nodejs server side 的渲染引擎 那里,转移到了 chromium 这里

简直就是:

又慢,又非常的耗内存

假如项目改造成本不高,建议直接上 ssr

假如需要采用这种方案的话,也比较适合做永久缓存,然后根据页面数据的变化,在推送 job 中手动刷新指定路由的缓存,然后去更新指定的 sitemap 中的 lastmod 字段 ,再 主动推送 给搜索引擎

而使用缓存过期的策略,不如每天凌晨,用 job 全部手动渲染,刷一遍缓存要好。

不过页面或者数据量大的情况下,也比较花时间花内存。

得不偿失。

附录

Github rendertron 项目地址

阿里云开发者社区 - 文章

本项目源码