Vue实现预渲染

前言

Ajax 技术的出现,让我们的 Web 应用能够在不刷新的状态下显示不同页面的内容,这就是单页应用。在一个单页应用中,往往只有一个 html 文件,然后根据访问的 url 来匹配对应的路由脚本,动态地渲染页面内容。单页应用在优化了用户体验的同时,也给我们带来了许多问题,例如 SEO 不友好、首屏可见时间过长等。服务端渲染(SSR)和预渲染(Prerender)技术正是为解决这些问题而生的。

SEO

SEO(Search Engine Optimization):汉译为搜索引擎优化。利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。

网络爬虫在爬取网页内容的时候,需要分析页面内容,主要有以下几点:
  1. 从 meta 标签中读取 keywords 、 description 的内容。
  2. 根据语义化的 html 的标签爬取和分析内容。一个整体都是用 div 标签的网站和正确使用了 html5 标签的效果是不一样的。
  3. 读取 a 标签里的链接,通过 a 标签的链接可以跳转到别的网站。(爬虫是先跳转,还是继续爬内容再跳转,就看算法是广度优先还是深度优先了)
  4. 像 h1 - h6 标签是具有不同程度的强调意义的。
  5. 一般将 h1 视为重要内容。同样有强调内容还有 strong 、 em 标签。
seo为啥对vue单页面不友好?
  1. 爬虫在爬取的过程中,不会去执行js,所以隐藏在js中的跳转也不会获取到。
  2. vue通过js控制路由然后渲染出对应的页面,而搜索引擎蜘蛛是不会去执行页面的js的,导致搜索引擎蜘蛛只能收录index.html一个页面,在百度中就搜索不到相关的子页面的内容。
  3. 我们加载页面的时候,浏览器的渲染包含:html的解析、dom树的构建、cssom构建、javascript解析、布局、绘制,当解析到javascript的时候才回去触发vue的渲染,然后元素挂载到id为app的div上,这个时候我们才能看到我们页面的内容,所以即使vue渲染机制很快我们仍然能够看到一段时间的白屏情况,用户体验不好。
引起的问题
  1. 收录的页面少了->被抓取的页面就少了->点击量之类的也就少了;
  2. 不能对对应的页面做TDK(title, keywords, description)不同的配置,每个页面的title和meta标签都是一样的,不利于网络爬虫的爬取。

服务端渲染与预渲染区别

客户端渲染:用户访问 url,请求 html 文件,前端根据路由动态渲染页面内容。关键链路较长,有一定的白屏时间;

服务端渲染:用户访问 url,服务端根据访问路径请求所需数据,拼接成 html 字符串,返回给前端。前端接收到 html 时已有部分内容;

预渲染:构建阶段生成匹配预渲染路径的 html 文件(注意:每个需要预渲染的路由都有一个对应的 html)。构建出来的 html 文件已有部分内容。

什么场景下不适合使用预渲染

个性化内容:对于路由是 /my-profile 的页面来说,预渲染就失效了。因为页面内容依据看它的人而显得不同;

经常变化的内容:如果你预渲染一个游戏排行榜,这个排行榜会随着新的玩家记录而更新,预渲染会让你的页面显示不正确直到脚本加载完成并替换成新的数据。这是一个不好的用户体验;

成千上万的路由:不建议预渲染非常多的路由,因为这会严重拖慢你的构建进程。

Prerender SPA Plugin

如果只是改善少数营销页面(例如 /, /about, /contact 等)的 SEO,那么你可能需要预渲染。无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时(build time)简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。

prerender-spa-plugin 是一个 webpack 插件用于在单页应用中预渲染静态 html 内容。因此,该插件限定了你的单页应用必须使用 webpack 构建,且它是框架无关的,无论你是使用 React 或 Vue 甚至不使用框架,都能用来进行预渲染。

prerender-spa-plugin 原理

那么 prerender-spa-plugin 是如何做到将运行时的 html 打包到文件中的呢?原理很简单,就是在 webpack 构建阶段的最后,在本地启动一个 phantomjs,访问配置了预渲染的路由,再将 phantomjs 中渲染的页面输出到 html 文件中,并建立路由对应的目录。

查看 prerender-spa-plugin 源码 prerender-spa-plugin/lib/phantom-page-render.js。


// 打开页面
page.open(url, function (status) {
  ...
  // 没有设置捕获钩子时,在脚本执行完捕获
  if (
    !options.captureAfterDocumentEvent &&
    !options.captureAfterElementExists &&
    !options.captureAfterTime
  ) {
    // 拼接 html
    var html = page.evaluate(function () {
      var doctype = new window.XMLSerializer().serializeToString(document.doctype)
      var outerHTML = document.documentElement.outerHTML
      return doctype + outerHTML
    })
    returnResult(html) // 捕获输出
  }
  ...
})

项目实例

安装

npm install prerender-spa-plugin --save

代码配置

vue.config.js

const PrerenderSPAPlugin = require('prerender-spa-plugin');

const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;

const path = require('path');

module.exports = {

  publicPath:'/',
  
  configureWebpack: config => {

​    config.entry = ['babel-polyfill', './src/main.js']if (process.env.NODE_ENV === 'production') {

​      config.plugins.push(new PrerenderSPAPlugin({// 生成文件的路径,也可以与webpakc打包的一致。// 下面这句话非常重要!!!// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。

​          staticDir: path.join(__dirname, '../el-table-selection/dist'),

​          indexPath: path.join(__dirname, '../el-table-selection/dist', 'index.html'),// 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。

​          routes: ['/jsbarcode', '/home', '/tableselection', '/example'],// renderer: new PrerenderSPAPlugin.PuppeteerRenderer({//这样写renderAfterTime生效了//   renderAfterTime: 5000// }),// renderAfterDocumentEvent: 'render-event'// 这个很重要,如果没有配置这段,也不会进行预编译

​          renderer: new Renderer({

​            inject: {

​              foo: 'bar'},

​            headless: false,// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。

​            renderAfterDocumentEvent: 'render-event'})}))}

  },

}

此项目的路径:

Vue实现预渲染_第1张图片

src/main.js

new Vue({

  router,

  render: h => h(App),

  mounted () {

​    document.dispatchEvent(new Event('render-event'))

  }

}).$mount('#app')

router.js

export default new Router({

  mode: 'history',

  routes

})

设置mode

预渲染的单页应用路由可以使用 history 模式或者 hash 模式。如果设置mode: ‘hash’,就要改一下预渲染的路由文件路径。

// 格式: /dist下对应的文件夹/#/页面路由
routes: ['/jsbarcode/#/home', '/home/#/home','/tableselection/#/home'],

验证是否配置成功

运行npm run build,看一下生成的 dist 的目录里是不是有每个路由名称对应的文件夹。然后找个 目录里 的 index.html 用IDE打开,看文件内容里是否有该文件应该有的内容。有的话,就设置成功了。

本项目运行结果:

Vue实现预渲染_第2张图片
可以看到已经打包成功了,接下来就是运行打包后的项目看看效果。

http-server的安装和使用

http-server是一个简单的node服务器,可以用它来运行打包后的项目。

安装:

npm i -g http-server

运行:(cd到dist文件下)

http-server

Vue实现预渲染_第3张图片

使用预渲染前后对比:

未使用预渲染,页面刷新时,会出现白屏

Vue实现预渲染_第4张图片

使用预渲染,页面刷新时,不会出现白屏

Vue实现预渲染_第5张图片

页面加载过程:打包后的html中的静态内容其实只起到一个占位图的效果,之后解析到javascript的时候再触发vue的渲染,生成可交互的页面并覆盖之前的静态页面。这种实现可以解决首屏显示时间过长的问题。

参考文献

Vue 预渲染实现方案

你可能感兴趣的:(vue)