serverless 场景下的 rendertron 方案
发布于 4 年前 作者 xiuying39 875 次浏览 来自 分享

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 项目地址

阿里云开发者社区-文章

本项目源码

回到顶部