预渲染 Vue SPA(踩坑记录)

June 14, 2018

使用 prerender-spa-plugin 在 Netlify 上对 Vue SPA 项目进行预渲染以优化 SEO。

最近一直在更新 Hubble 上的 React vs. Vue 100k star race LIVE 忙的不亦乐乎。 昨天 Vue 与 React 的差距缩小到 500 以内,想着终于到了加上 Facebook 和 Twitter 分享按钮的时候了,可以吸引一些观众。

按照 Facebook 和 Twitter 各自的文档,配好了相应的链接和 <meta> 标签(使用 vue-meta),然而实际效果却与预期有些差距。 Hubble 主站的 title 和 description 分别是 "Hubble" 和 "Travel through GitHub Stars' History",而在 React vs. Vue 的页面,本应已经分别设为 "React vs. Vue · Hubble" 和 "100k stars race between React and Vue is LIVE now."。 然而按照 Facebook 的文档配好了 title 和 description 后,分享出去的链接里仍然显示原来的文字。 Facebook share 问题就出在 SEO,解决的方法就是使用 SSR 或者进行预渲染。

那么首先看看 Vue 的文档怎么说。 文档里说如果只是为了 SEO,那么可以不必使用 SSR,只需要配合 webpack 使用 prerender-spa-plugin 就可以。 按照插件的文档给出的示例来配置 webpack

{
  plugins: [
    new PrerenderSPAPlugin({
      staticDir: path.join(__dirname, "../dist"),
      routes: ["/react-vs-vue"],
      renderer: new Renderer({
        inject: {},
        headless: false,
        renderAfterDocumentEvent: "render-event",
      }),
    }),
  ]
}

要注意 Vue-Router 只有在 history 模式下才支持 SSR 和预渲染,因此要做一些调整,并且把原来代码里的一些链接改掉。 修改完后,提交代码。

接着要在 Netlify 上开启预渲染,具体位置在 Settings->Build & deploy Enable prerendering on Netlify

推送分支,让 Netlify 开始构建。

然后报错。:)

1:30:14 AM: > node build/build.js
1:31:20 AM: Error: Failed to launch chrome!
1:31:20 AM: [0613/173120.242750:ERROR:nacl_helper_linux.cc(310)] NaCl helper process running without a sandbox!
1:31:20 AM: Most likely you need to configure your SUID sandbox correctly
1:31:20 AM: TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
1:31:20 AM:     at onClose (/opt/build/repo/node_modules/puppeteer/lib/Launcher.js:285:14)
1:31:20 AM:     at Interface.helper.addEventListener (/opt/build/repo/node_modules/puppeteer/lib/Launcher.js:274:50)
1:31:20 AM:     at emitNone (events.js:111:20)
1:31:20 AM:     at Interface.emit (events.js:208:7)
1:31:20 AM:     at Interface.close (readline.js:368:8)
1:31:20 AM:     at Socket.onend (readline.js:147:10)
1:31:20 AM:     at emitNone (events.js:111:20)
1:31:20 AM:     at Socket.emit (events.js:208:7)
1:31:20 AM:     at endReadableNT (_stream_readable.js:1064:12)
1:31:20 AM:     at _combinedTickCallback (internal/process/next_tick.js:138:11)
1:31:20 AM:     at process._tickCallback (internal/process/next_tick.js:180:9)
1:31:20 AM: [Prerenderer - PuppeteerRenderer] Unable to start Puppeteer
1:31:20 AM: (node:1246) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'close' of null
1:31:20 AM:     at PuppeteerRenderer.destroy (/opt/build/repo/node_modules/@prerenderer/renderer-puppeteer/es6/renderer.js:139:21)
1:31:20 AM:     at Prerenderer.destroy (/opt/build/repo/node_modules/@prerenderer/prerenderer/es6/index.js:87:20)
1:31:20 AM:     at PrerendererInstance.initialize.then.then.then.then.then.then.then.then.catch.err (/opt/build/repo/node_modules/prerender-spa-plugin/es6/index.js:145:29)
1:31:20 AM:     at <anonymous>
1:31:20 AM:     at process._tickCallback (internal/process/next_tick.js:188:7)

看起来很明显是 puppeteer 出了什么问题。 跟着错误信息里给出的链接,找到相关的文档,里面提到了类似的问题。 解决方法是启动 puppeteer 时传入 --no-sandbox --disable-setuid-sandbox 两个 flag。

那么就需要去查看 prerender-spa-plugin 的文档了,因为 puppeteer 是由它启动的。 在 README 中可以看到,可以向 PuppeteerRenderer 的构造函数传入任何 puppeteer.launch() 所接受的选项。据此修改 webpack 配置如下

new PrerenderSPAPlugin({
    staticDir: path.join(__dirname, '../dist'),
    routes: [ '/react-vs-vue' ],
    renderer: new Renderer({
        inject: {
        },
        headless: false,
        renderAfterDocumentEvent: 'render-event',
+       args: ['--no-sandbox', '--disable-setuid-sandbox'],
    })
})

提交代码并推送,让 Netlify 再次构建。

并报错。:)

打开错误日志,发现和上次一样,说明同样的问题并没解决。那么只好在 prerenderer-spa-plugin 的 issues 里面搜下。 一搜果然还就搜到了。在评论中一位 collaborator 指出,如果出现这个问题,要去掉 Renderer 选项中的 headless: false

new PrerenderSPAPlugin({
    staticDir: path.join(__dirname, '../dist'),
    routes: [ '/react-vs-vue' ],
    renderer: new Renderer({
        inject: {
        },
-       headless: false,
        renderAfterDocumentEvent: 'render-event',
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
    })
})

再提交推送试一次。这次成功了,虽然并不懂为什么。:)

现在再点击 Facebook 分享按钮,分享的链接已经可以爬取正确的 title 和 description 了。 Facebook share with prerendering

最近可能再写一篇关于 Hubble 以及 React vs. Vue 本身的博客。

耶。


Profile picture

Written by Doma who just migrated his blog to Gatsby.js. You should follow him on Twitter and GitHub.