前端知识框架 前端知识框架
首页
基础
框架
插件
Node
地图
更多
前端须知
  • 分类
  • 标签
  • 归档

BestIdea

首页
基础
框架
插件
Node
地图
更多
前端须知
  • 分类
  • 标签
  • 归档
  • 微前端 qiankun
  • 在线预览Java后台源码
  • 如何优雅的修改node_modules依赖源码
  • seo优化解决方案
    • 更多
    Btzh
    2022-05-26
    目录

    seo优化解决方案

    # 需求背景

    企业微信机器人设置自动回复链接会先检索链接的页面标题、图片、描述等,用来生成自动回复链接卡片(如图)

    以上需求实际上可归结为SEO( 搜索引擎优化 )问题

    # 什么是seo?

    SEO(Search Engine Optimization):汉译为搜索引擎优化。是一种方式:利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。目的是让其在行业内占据领先地位,获得品牌收益。很大程度上是网站经营者的一种商业行为,将自己或自己公司的排名前移。 --百度百科

    # 提出问题

    前端三大框架Vue、React 、Angular的广泛应用,其单页面形式的普及对有SEO的需求极不友好,原因在于seo在爬虫时不会执行JS。在vue中我们通常使用Vue Router控制路由渲染对应的页面,所以搜索引擎只会收录到 index.html一个页面,且检索到的只能是初始状态一些固定内容,不能对对应的页面做TDK(title, keywords, description)不同的配置,每个页面的title和meta标签都是一样的。

    # 解决方案

    1. 服务器端渲染(SSR)

      服务端渲染的模式下,当⽤户第⼀次请求页⾯时,由服务器把需要的组件或页⾯渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到⼿的,是可以直接渲染然后呈现给⽤户的 HTML 内容,不需要为了⽣成 DOM 内容⾃⼰再去跑⼀遍 JS 代码。使⽤服务端渲染的⽹站,页⾯上呈现的内容,我们在 html 源⽂件⾥也能找到。

      **优点:**⾸屏渲染快、利于SEO

      **缺点:**服务端压力较大、学习成本相对较高

    2. 客户端预渲染

      通过PrerenderSPAPlugin插件在构建(build)时简单地生成针对特定路由的静态 HTML 文件,配合vue-meta在所需页面内设置不同的标题、描述等信息。

      PrerenderSPAPlugin原理:( 在webpack打包结束并生成文件后(after-emit hook),会启动一个server模拟网站的运行,用puppeteer(google官方的headless 无头浏览器)访问指定的页面route,得到相应的html结构,并将结果输出到指定目录,过程类似于爬虫。 )

      **优点:**⾸屏渲染快、利于SEO

      **缺点:**方案尚未成熟可能会遇到一些无法预期的错误。

      ​ 需要更改路由模式,老旧项目可能会造成关联影响。

    3. 多页面打包

      通过在根目录设置多个html文件,打包生成多个不同title的index.html用来满足seo需求,后续再配合路由监听的动态更改title。

      优点:利于SEO,部署简单,适合对seo需求页面少的项目

      缺点:不能提升首屏渲染速度,同项目不同页面地址前缀不统一

    综上,方案一因其缺点明显,本章不再讨论。

    # 方案2:客户端预渲染

    1. 安装

      npm i prerender-spa-plugin
      
      1
      npm i vue-meta
      
      1
    2. router.js

      const router = new Router({
        mode: 'history',  //vue-router 路由模式改为history
        routes:[...]
      })
      
      1
      2
      3
      4
    3. main.js

      import Vue from 'vue'
      import VueMeta from 'vue-meta'
       
      Vue.use(VueMeta, {
        // optional pluginOptions
        refreshOnceOnNavigation: true
      })
      
      ...
      
      new Vue({
          store,
          router,
          render: h => h(App),
          mounted: () => document.dispatchEvent(new Event('render-event'))
        }).$mount('#app')
      }
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
    4. 有seo需求的页面

        export default {
          metaInfo: {
            title: 'MyPage',
            meta: [
            	{ charset: 'utf-8' },
              { name: 'description', content: 'page description' }
              {name: 'keyWords',content: 'My Example App'}
            ]
            link: [{                
              rel: 'asstes',
              href: 'https://assets-cdn.github.com/'
            }]
            htmlAttrs: {
              lang: 'en',
            }
          }
        }
        
      // 使用异步数据
      export default {
        data () {
          return {
            title: 'Foo Bar Baz'
          }
        },
        metaInfo () {
          return {
            title: this.title
          }
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
    5. webpack.config.js

      const path = require('path')
      const PrerenderSPAPlugin = require('prerender-spa-plugin')
      
      module.exports = {
        plugins: [
          ...
           new PrerenderSPAPlugin({
            // webpack输出的app预渲染的路径.
            staticDir: path.join(__dirname, '../dist'),
            // 需要预加载页面的路由.
             routes: ['/','/chat'],
             renderer: new  PrerenderSPAPlugin.PuppeteerRenderer({
                      inject: {
                        foo: 'bar'
                      },
      // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
               renderAfterDocumentEvent: 'render-event'
             }),
             navigationOptions: {
              timeout: 0,
            }
          }),
        ]
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
    6. 打包结果

      示例2

    生成以需要预加载页面文件名命名的文件夹,其中的index.html 即预渲染生成的页面

    # 方案2:避坑指南

    1. 在Linux (CentOS)系统下,打包 prerender-spa-plugin 插件时报错:

      [Prerenderer - PuppeteerRenderer] Unable to start Puppeteer Error: Failed to launch chrome! /server/jenkins/.jenkins/workspace/dlej-h5paas_sfapp_tj-dlej-h5_dev/node_modules/puppeteer/.local-chromium/linux-686378/chrome-linux/chrome: error while loading shared libraries: libXss.so.1: cannot open shared object file: No such file or directory

      解决:在Linux 系统下安装依赖包,命令如下:

      yum install libXScrnSaver atk java-atk-wrapper at-spi2-atk gtk3 libXt -y
      
      1
    2. prerender-spa-plugin插件是需要依赖puppeteer (opens new window)的,即谷歌出品的无头浏览器插件,这个插件会下载最新版的chromium(大约300M)。如此大的插件会导致项目过于臃肿。

    3. TypeError: Cannot read property 'close' of undefined

      解决:node_modules中 @prerenderer/renderer-puppeteer/es6/renderer.js 140行 this._puppeteer.close() 更改为,源码修改方法见文章: 如何优雅的修改node_modules依赖源码 (opens new window)

         setTimeout(() => {
           this._puppeteer.close()
         }, 500)
      
      1
      2
      3
    4. 打包后页面js无响应

      解决:检查预渲染打包出来的html是不是少了id="app"的元素,如缺少需要在根组件添加id=‘app’,

      <div id='app'>
          <router-view />
      </div>
      
      1
      2
      3
    5. 对于老旧项目,路由模式的切换可能会导致一些关联的产品地址失效。需提前评估该方案可行性。如只是个别页面有seo需求,建议采用方案3。

    # 方案3:多页面打包

    1. 根目录下复制index.html,如下图indexQA.html,在复制出的html文件中更改title、description等信息

      1653548474293

    2. webpack.config.js

        plugins:[
          new HtmlWebpackPlugin({
            filename: path.resolve(__dirname, '../dist/index.html'),
            template: 'index.html',
            inject: true,
            cache: true,
            ...
            }),
            
          // 额外的页面打包
          new HtmlWebpackPlugin({
            filename: path.resolve(__dirname, '../dist/chat/index.html'),
            template: 'indexQA.html',
            inject: true,
            cache: true,
            ...
            }), 
            ...
          ]
          
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
    3. 打包实际部署

      打包后的目录结构同方案2中的打包结果,html中内容即为根目录下indexQA.html打包后内容。

      此时除原项目地址外,在项目原地址后添加 /chat/打开的也是原项目,不同在于seo检索时两个地址检索到的页面信息不同。

    上次更新: 2022/05/26, 17:29:11
    如何优雅的修改node_modules依赖源码

    ← 如何优雅的修改node_modules依赖源码

    最近更新
    01
    webpack打包替换类名命名空间
    05-01
    02
    Vite常用配置
    02-26
    03
    crypto前端加密
    01-18
    更多文章>
    Theme by Vdoing | Copyright © 2022-2024
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式