对前端渲染的项目而言搜索引擎优化或许是令许多开发者望而却步的原因即便目前一些搜索引擎会在抓取网页时启用JavaScript但其渲染的结果是不可预知的——一旦因为某些原因没有渲染出正确的结果搜索引擎将对我们的网站一无所知更靠谱的做法是当搜索引擎的爬虫访问时直接将渲染后的结果丢给它

服务端渲染是一种方案然而它要求服务端与客户端的渲染层是同构的只有这样服务端才能使用客户端的代码渲染出类似的结果在这方面JavaScript具有天然的优势不依赖于BOMDOM的前端代码几乎都可以直接在Node.js中执行ReactVue都引入了虚拟DOM的概念使用者在绝大多数情况下可以避免直接与DOM打交道这也让服务端渲染变得更简单

另一种方案是使用Prerender对页面进行预渲染其原理比较简单大致是启动一个支持JavaScript的浏览器来访问想要预渲染的页面在渲染完毕后读取最终的HTML代码如果写成表达式大致是

new XMLSerializer().serializeToString(document.doctype)
  + document.documentElement.outerHTML

对比服务端渲染和Prerender其实Prerender反而更适合用于搜索引擎优化

一方面是因为从架构上来看Prerender是一层单独的服务普遍适用于各种类型的前端渲染页面而服务端渲染从某种意义上而言是耦合在前端代码之中的要求渲染层同构另一方面是因为服务端渲染的某些步骤对搜索引擎而言没有那么重要——搜索引擎只需要拿到页面渲染后的静态HTML代码能够分析出页面上的链接就可以正常收录页面了而服务端渲染为确保客户端与其渲染后的状态能够同步还做了一些额外的工作甚至为了避免爬虫执行JavaScript代码Prerender默认会移除渲染后的HTML代码中的所有<script>标签

当然Prerender也是有不足的作为服务Prerender跟前端代码是低耦合的最明显的问题就是难以知晓页面什么时候渲染完成Prerender是根据网络请求来判断的如果一段时间内都没有任何请求和AJAX调用就判定页面渲染完成默认情况下Prerender会等待500毫秒这会导致搜索引擎在请求页面时响应比较缓慢建议适当调低等待时间并且开启缓存

Prerender底层使用的是PhantomJS如果需要针对Prerender做一些特殊处理判断navigator.userAgent即可比如Prerender渲染时跳过代码高亮的步骤返回干净的代码

const isPrerender = /\bPhantomJS\b/.test(navigator.userAgent)

const renderer = new marked.Renderer()

if (!isPrerender) {
  renderer.code = (code, language) => {
    return `<pre><code class="hljs ${language}">${hljs.highlightAuto(code).value}</code></pre>`
  }
}

marked.setOptions({ renderer })

另外PhantomJS 2.5才开始支持ES2015目前需要使用一些polyfills包括绝大多数现代浏览器都已经支持了的Promise