探究SEO与VUE首屏渲染及其解决方案

4,483 阅读6分钟

前言

  • 【音乐博客】上线啦!
  • 开开心心部署到服务器,然后满怀欣喜打开首页,结果发现有点小慢,这就很不愉快啦
  • 下面来解决vue首屏渲染


五个方面

1. SPA单页面应用

  • 首屏打开速度很慢,因为用户首次加载需要先下载SPA框架及应用程序的代码,然后再渲染页面。
  • 不利于SEO
  • 根据这几个缺点,我们便又引出了接下来讨论的两个主题,SEO和SSR,先谈谈SEO。

2. SEO搜索引擎

  • 搜索引擎优化。SEO是一种通过了解搜索引擎的运作规则(如何抓取网站页面,如何索引以及如何根据特定的关键字展现搜索结果排序等)来调整网站,以提高该网站在搜索引擎中某些关键词的搜索结果排名。
  • Vue SSR说:
如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。
  • 那搜索引擎不支持ajax获取数据,那就更不用提SEO了
  • 对于有些网站而言,SEO显得至关重要,例如主要以内容输出为主的Quorastackoverflow知乎豆瓣等等,那如何才能正常使用SPA而又不影响SEO呢?鲁迅曰:
  • 技术上的问题总有技术去解决
  • 此时,SSR便闪亮登场了

3. SSR服务端渲染

  • 在普通的SPA中,一般是框架及网站页面代码发送给浏览器,然后在浏览器中生成和操作DOM(这也就是为什么第一次SPA网站在同等带宽下比传统的在后端生成HTML发送到浏览器要更慢的主要原因),但其实也可以将SPA应用打包到服务器上,在服务器上渲染出HTML,发送到浏览器,这样的HTML页面还不具备交互能力,所以还需要与SPA框架配合,在浏览器上“混合”成可交互的应用程序。所以,只要能合理地运用SSR技术,不仅能一定程度上解决首屏慢的问题,还能获得更好的SEO。
  • SSR的优点
  • 更快的响应时间,不用等待所有的JS都下载完成,浏览器便能显示比较完整的页面了。这个个人深有体会,我的个人博客最开始仅仅使用了Vue.js,而没有做服务端渲染,加之服务器不在大陆,第一次输入地址到看到完整的页面几乎是过了4、5秒,有时候还更长。
  • 更好的SSR,我们可以将SEO的关键信息直接在后台就渲染成HTML,而保证搜索引擎的爬虫都能爬取到关键数据。
  • SSR的缺点
  • 相对于仅仅需要提供静态文件的服务器,SSR中使用的渲染程序自然会占用更多的CPU和内存资源
  • SSR常用框架
  • React 的 Next
  • Vue.js 的 Nuxt
  • 所以要想SEO做的好,建议使用服务端SSR渲染(网页是通过服务端渲染生成后输出给客户端),可采用Nuxt(其本质是Node封装的VUE的SSR框架,所以是在服务端跑的,对首屏渲染友好)

4. 预编译prerender-spa-plugin插件

  • Nuxt要用他的那套约束,也就是说你的项目要迁移到Nuxt去,解决 SEO 问题是不是只有 SSR 呢?其实预渲染也能做到
  • 还有的,就是webpack的预编译,可以对首页先加载出来,然后他再慢慢去加载js文件,这样就不用等待首页加载
  • 服务端渲染解决的问题,不仅只是把 HTML 页面给浏览器,更重要的是处理动态逻辑和 JS 代码后,将渲染后完整的 HTML 给浏览器,渲染的过程在服务端。
  • 预渲染,是利用构建工具在 webpack 中生成静态的 HTML,直接给浏览器,渲染的过程在本地。
  • 预渲染插件里面提到两种不能使用:大量路由、动态内容
  • 接下来我们介绍下prerender-spa-plugin插件实现预编译
  • 安装:
  • cnpm install prerender-spa-plugin --save

  • vue-cli3.0的写法
  • vue-config.js中增加
  • const PrerenderSPAPlugin = require('prerender-spa-plugin');
    const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
    const path = require('path');
    module.exports = {
        configureWebpack: config => {
            if (process.env.NODE_ENV !== 'production') return;
            return {
                plugins: [
                    new PrerenderSPAPlugin({
                        // 生成文件的路径,也可以与webpakc打包的一致。
                        // 下面这句话非常重要!!!
                        // 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
                        staticDir: path.join(__dirname,'dist'),
                        // 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
                        routes: ['/', '/product','/about'],
                        // 这个很重要,如果没有配置这段,也不会进行预编译
                        renderer: new Renderer({
                            inject: {
                                foo: 'bar'
                            },
                            headless: false,
                            // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
                            renderAfterDocumentEvent: 'render-event'
                        })
                    }),
                ],
            };
        }
    }

  • 在main.js中增加
  • new Vue({
      router,
      store,
      render: h => h(App),
      mounted () {
        document.dispatchEvent(new Event('render-event'))
      }
    }).$mount('#app')

  • router.js 中设置mode: “history”
    注意:官方文档上 路由模式必须为 history 。如果不设置history模式,也能运行和生成文件,每个index.html文件的内容都会是一样的。所以必须使用 history模式
  • 运行npm run build,看一下生成的 dist 的目录里是不是有每个路由名称对应的文件夹。然后找个 目录里 的 index.html 用IDE打开,看文件内容里是否有该文件应该有的内容。有的话,就设置成功了
  • 打包之后如下图:


  • 突然发现原来我一直有使用Gzip压缩文件的,这就很好了

  • vue-cli2.0的写法
  • 修改 webpack 配置,比较简单就能完成配置
  • const PrerenderSPAPlugin = require('prerender-spa-plugin')
      const Renderer = PrerenderSPAPlugin.PuppeteerRenderermodule.exports = {
        plugins: [
          //...   
          new PrerenderSPAPlugin({
            staticDir: path.join(__dirname, 'dist'),
            outputDir: path.join(__dirname, 'prerendered'),
            indexPath: path.join(__dirname, 'dist', 'index.html'),
            routes: ['/', '/about', '/some/deep/nested/route'],
            postProcess(renderedRoute) {
              renderedRoute.route = renderedRoute.originalPath
              renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
              if (renderedRoute.route.endsWith('.html')) {
                renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.route)
              }
              return renderedRoute
            }, minify: {
              collapseBooleanAttributes: true,
              collapseWhitespace: true,
              decodeEntities: true, keepClosingSlash: true,
              sortAttributes: true
            },
            renderer: new Renderer({
              inject: { foo: 'bar' },
              maxConcurrentRoutes: 4
            })
          })
        ]
      }

  • 简直不要太爽,速度变快了很多,基本上1s就可以显示首页页面,有兴趣的小伙伴赶紧使用在项目里面吧!!!

5. 两者的区别

  • 服务端SSR渲染解决的问题,不仅只是把 HTML 页面给浏览器,更重要的是处理动态逻辑和 JS 代码后,将渲染后完整的 HTML 给浏览器,渲染的过程在服务端。
  • 预渲染,是利用构建工具在 webpack 中生成静态的 HTML,直接给浏览器,渲染的过程在本地。将服务端编译HTML的时机提前到了构建时,因此降低了服务端的压力
  • 共性:
    加载速度一样快;
    入侵性小;
  • 不同:
    服务端渲染在服务器;适用于复杂、较大型、与服务端交互频繁的网站,如电商
    预渲染在客户端上;适用于简单的静态页面
  • 很奇怪的一点是:打开f12,发现刷新很慢,关掉f12,刷新就很快,不明觉厉,知道的同学在评论区分享下!


最后总结

要想seo好,使用ssr服务端渲染,即服务端生成静态页面给客户端,交互的请结合Nuxt;或者不在服务端渲染,使用webpack的prerender-spa-plugin插件实现预编译,生成静态的 HTML,直接给浏览器,渲染的过程在本地。使用webpack的code splitting按需加载压缩优化首页渲染或者vue分片


参考

vue-cli3.0使用prerender-spa-plugin插件预渲染 :blog.csdn.net/lv5751394/a…

使用 prerender-spa-plugin 插件进行简单预渲染vue-cli2.0:juejin.cn/post/684490…