vue项目预渲染

313 阅读4分钟

SEO相关知识

简介

搜索引擎优化(Search engine optimization,简称SEO),指为了提升网页在搜索引擎自然搜索结果中(非商业性推广结果)的收录数量以及排序位置而做的优化行为,是为了从搜索引擎中获得更多的免费流量,以及更好的展现形象。

原理

搜素引擎过程非常复杂,简略的表达一下就是:

  1. 爬行和抓取:搜索引擎通过跟踪链接访问网页、获得页面 HTML 代码并存入数据库。所以提升自己网站的外链很重要,比如跟某些导航网站合作,添加外链
  2. 数据处理:各个爬虫对HTML的内容进行抓取,对抓取的数据进行文字提取、中文分词、索引等处理,存入数据库
  3. 排名:用户输入关键词后,排名程序调用索引数据库,计算相关性,然后按照一个的格式生成搜索结果页面。

提升SEO的优化手段

  1. 砸钱,对搜索引擎(例如百度)等进行商务洽谈,给钱提升SEO排名
  2. 多使用HTML语义化标签
  3. 良好的网站结构,最多三层,层次分明,以便爬虫更好的爬取
  4. 设置良好的 meta ,title 等
  5. 提交 Sitemap
  6. 对于前后端分离的项目,做到预渲染 上述优化策略来源于网络与谷歌SEO优化指南
    下面我们针对第六点进行实现

预渲染

对于前后端分离的项目我们有两种方法进行预渲染

  1. 服务端预渲染:对于 vue 我们使用 nuxt 框架进行预渲染,对于 react 我们使用 nest 框架进行预渲染
  2. SPA项目预渲染:对于 SPA 应用我们采取的是打包预渲染,即在打包时对关键页面进行渲染
    下面我们针对 第二点进行 具体的实现

使用 prerender-spa-plugin SPA项目预渲染

prerender-spa-plugin项目文档

项目要求
  • 项目路由必须为 history 模式
  • 项目打包工具必须为 webpack
依赖
npm i prerender-spa-plugin -D

or

yarn add prerender-spa-plugin -D
vue.config.js 设置
// vue.config.js
const path = require('path')
const PrerenderSPAPlugin = require('crd-prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

function resolve (dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  publicPath: './',
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new PrerenderSPAPlugin({
            staticDir: resolve('dist'),
            routes: ['/', '/test'], // 你需要预渲染的路由
            renderer: new Renderer({
              inject: {
                _m: 'prerender'
              },
              // 渲染时显示浏览器窗口,调试时有用
              headless: true,
              renderAfterDocumentEvent: 'render-event'
            })
          })
        ]
      }
    }
  }
}
main.js 设置
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
  router,
  store,
  render: h => h(App),
  mounted(){
    // 当 挂载之后,通过 发布订阅 通知 触发 render-event 这里的 render-event 需要与 vue.config.js 中保持一致
    document.dispatchEvent(new Event('render-event'))
  },
}).$mount('#app')

原理

prerender-spa-plugin 通过 谷歌提供的无头浏览器puppeteer,提前渲染了页面之后,抓包生成对应路由的 html

优缺点分析
优点
  1. 利于单页面的SEO
  2. 代码改动不大,相对于 nuxt 方案来说
  3. 减少白屏时间
缺点
  1. 对于 hash 模式的 spa项目改动较大
  2. 无法适用于新型打包工具 vite
踩坑

[prerender-spa-plugin] Unable to prerender all routes,webpack版本过高导致失败,我们来深入 prerender-spa-plugin 的源码看看到底是什么原因,在 /node_modules/crd-prerender-spa-plugin/index.js

// Is there a better way to check versions? Haven't really looked into it.
if (parseInt(process.versions.node.split('.')[0]) >= 8) {
 // Native (Node 8+) ES6. (Requires async / await.)
 module.exports = require('./es6/index.js')
} else {
 // Transpiled through babel to target Node 4+.
 module.exports = require('./es5-autogenerated/index.js')
}

由代码我们可以知道,当 Node 版本大于 8的时候,走的就是 ./es6.index.js;小于的话,走的就是./es5-autogenerated/index.js
./es6.index.js全局搜索错误:[prerender-spa-plugin] Unable to prerender all routes 发现:

      .then(r => {
       const { successCb } = this._options
       successCb && successCb()
       PrerendererInstance.destroy()
       done()
     })
     .catch(err => {
       PrerendererInstance.destroy()
       const msg = `[prerender-spa-plugin] Unable to prerender all routes! [Error: ${err}]`
       console.error(msg)
       compilation.errors.push(new Error(msg))
       done()
     })

原来是错误被吞掉了,没有导致控制台打印error错误,我们添加 console.error 打印错误,执行yarn build 发现错误原来是:TypeError: compilerFS.mkdirp is not a function
我们查看 compilerFS.mkdirp

PrerenderSPAPlugin.prototype.apply = function (compiler) {
   const compilerFS = compiler.outputFileSystem
   const mkdirp = function (dir, opts) {
       return new Promise((resolve, reject) => {
           compilerFS.mkdirp(dir, opts, (err, made) => (err === null ? resolve(made) : reject(err)))
       })
   }
   .....
}

问题就出现在这里, compiler.mkdirp方法已经被 webpack5.x 弃用,改为compiler.mkdir即可,webpack4.x 版本使用则没有问题 下面给出解决方法:

  1. 替换包prerender-spa-plugincrd-prerender-spa-plugin,此包的作者兼容了 webpack5.x
  const mkdirp = function (dir, opts) {
    return new Promise((resolve, reject) => {
      try {
        compilerFS.mkdirp(dir, opts, (err, made) => (err === null ? resolve(made) : reject(err)))
      } catch (e) {
        // mkdirp removed in Webpack 5
        compilerFS.mkdir(dir, { ...opts, recursive: true }, (err, made) => (err === null ? resolve(made) : reject(err)))
      }
    })
  }

但是这个方法也有缺点,毕竟是 fork prerender-spa-plugin 的代码,当 prerender-spa-plugin包继续更新时,会不能享受新的特性。 2. 编写脚本 手动变更 mkdirpmkdir 3. webpack 降级处理 异步数据无法渲染:我们发现在预渲染之后,异步数据无法被渲染出来,经过排查之后我们发现,主要是有两个。

  1. 触发时机:即使是我们在 $nextTick之后再触发renderAfterDocumentEvent,也获取不到异步数据
  2. 异步请求网络问题,异步数据一般都是通过ajax获取,可能涉及到跨域的问题,接口的cookie鉴权等等。 解决办法:
  3. renderAfterDocumentEvent 触发更换renderAfterTime触发,注意当两者都存在时,renderAfterTime将被忽略。
  4. prerender-spa-plugin包内置了expresshttp-proxy-middleware包,向外暴露的配置为serve选项,为了解决跨域的问题,我们增加一项serve的proxy配置:
        server:{
          proxy:{
            '/v1/supplier/': {
              target:"https://xxx.com/",
              changeOrigin:true
            },
          }
        },

根据上方的配置,我们针对此包获取异步渲染数据做一个总结:能进行异步数据渲染,但是缺点比较大,一、由于我们只能使用 renderAfterTime 延时触发,所以接口响应时间与renderAfterTime配合好,但是renderAfterTime过长又会导致打包时间过长;二、由于是在node端进行ajax的请求,所以我们的接口鉴权机制需要讨论(不能使用cookie方式) 顺便提一句,这个包的错误处理方式不是很好,很多错误都需要去看源码debugger才能了解 ==