preface

Since React, Vue and Angular came out, front-end development tends to be standardized and unified. Most of the time when a new front-end project is created, the first technology that comes to mind must be one of the three frameworks. Frameworks bring great convenience and standardization to front-end development, but because these three frameworks are jS-driven, Before JS is parsed and loaded, the page cannot be displayed and will remain blank for a long time, which brings certain user experience problems. Next, this article will introduce some problems and thoughts I have recently encountered in blank screen optimization

SSR

Think of bad problems, the first thought of solution are generally render the service side, on the server will be rendering logic to deal with, then will handle good HTML directly back to the front, so that you can solve the problem of bad, can also solve the problem of seo, because they don’t need dynamic access to data, however, this and I write the back-end of the early development mode, Front-end and back-end association together, is not conducive to maintenance, at the same time, for the front-end engineer, requirements get higher, need to know about the back-end of knowledge, although there is a similar Nuxt. Js SSR framework of this kind of help we simplified the server-side part, but the custom to do or solve the bug still cannot avoid to part of a service for debugging, maintenance, The cost is high, and there are server side renderings that add strain to the server, dealing with concurrency, speed issues, etc

pre-rendered

This solution is a relatively straightforward and inexpensive solution to try. Here’s how to pre-render using prerender-spa-plugin so that you can render in the browser without having to deploy Vue or React code to the server. The following uses the official demo of VUE-CLI3 as an example to perform the configuration.

const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
  configureWebpack: config= > {
    let plugins = []
    plugins.push(new PrerenderSPAPlugin({
      staticDir: path.resolve(__dirname, 'dist'),
      routes: ['/'.'/about'].minify: {
        collapseBooleanAttributes: true.collapseWhitespace: true.decodeEntities: true.keepClosingSlash: true.sortAttributes: true
      },
      renderer: new Renderer({
        renderAfterDocumentEvent: 'custom-render-trigger'
      })
    }))
    config.plugins = [
      ...config.plugins,
      ...plugins
    ]
  }
}
Copy the code

Prerender-spa-plugin, staticDir prerender output file address, routes prerender route, Minify compression configuration, Renderer engine configuration, RenderAfterDocumentEvent is a property in the render engine configuration that prerenders when an event is triggered. It is important to have a complete description of the properties of the render engine. In particular, for certain scenarios, it is possible to use the default renderer option for simple scenarios without setting renderer engine options. So let’s compile and see what happens. Okay?

  <div id="app">
   <div id="nav">
    <a href="/" class="router-link-exact-active router-link-active">Home</a> | 
    <a href="/about" class="">About</a>
   </div>
   <div class="home">
    <img alt="Vue logo" src="/img/logo.82b9c7a5.png" />
    <div class="hello" data-v-7b2de9b7="">
     <h1 data-v-7b2de9b7="">Welcome to Your Vue.js App</h1>
     <p data-v-7b2de9b7="">For a guide and recipes on how to configure / customize this project,<br data-v-7b2de9b7="" />check out the <a href="https://cli.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-cli documentation</a>.</p>
     <h3 data-v-7b2de9b7="">Installed CLI Plugins</h3>
     <ul data-v-7b2de9b7="">
      <li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" data-v-7b2de9b7="" rel="noopener" target="_blank">babel</a></li>
      <li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" data-v-7b2de9b7="" rel="noopener" target="_blank">eslint</a></li>
      <li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest" data-v-7b2de9b7="" rel="noopener" target="_blank">unit-jest</a></li>
     </ul>
     <h3 data-v-7b2de9b7="">Essential Links</h3>
     <ul data-v-7b2de9b7="">
      <li data-v-7b2de9b7=""><a href="https://vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">Core Docs</a></li>
      <li data-v-7b2de9b7=""><a href="https://forum.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">Forum</a></li>
      <li data-v-7b2de9b7=""><a href="https://chat.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">Community Chat</a></li>
      <li data-v-7b2de9b7=""><a href="https://twitter.com/vuejs" data-v-7b2de9b7="" rel="noopener" target="_blank">Twitter</a></li>
      <li data-v-7b2de9b7=""><a href="https://news.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">News</a></li>
     </ul>
     <h3 data-v-7b2de9b7="">Ecosystem</h3>
     <ul data-v-7b2de9b7="">
      <li data-v-7b2de9b7=""><a href="https://router.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-router</a></li>
      <li data-v-7b2de9b7=""><a href="https://vuex.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vuex</a></li>
      <li data-v-7b2de9b7=""><a href="https://github.com/vuejs/vue-devtools#vue-devtools" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-devtools</a></li>
      <li data-v-7b2de9b7=""><a href="https://vue-loader.vuejs.org" data-v-7b2de9b7="" rel="noopener" target="_blank">vue-loader</a></li>
      <li data-v-7b2de9b7=""><a href="https://github.com/vuejs/awesome-vue" data-v-7b2de9b7="" rel="noopener" target="_blank">awesome-vue</a></li>
     </ul>
    </div>
   </div>
  </div>
Copy the code

For convenience, only the code in the APP node is pasted here. In the past, there was no content in the APP node when the pre-rendering plug-in was not used. From the loading of the index.html file to the completion of the js file parsing, the app node was empty, so the page would be in a blank state. After the js file is parsed, Vue will replace the content in the APP node with the content rendered by Vue. Let’s see what the difference is under Chrome debugging:

  • Dynamic data is not displayed, and different users see the same page
  • When there are many routes, the code takes too long to build
  • Users are prone to misoperation. Because JS has not been loaded during pre-rendering, the content displayed does not have JS interaction logic, such as button clicking, and there is no reaction when users click before JS loading is completed
  • There is very little preloaded content. When a page has content that depends on dynamic data loading, dynamic data cannot be loaded during compilation, so this part of content cannot be compiled

Skeleton screen

The skeleton screen works in a similar way to preloading, using the page crawl function of Puppeteer, a headlessChromenode library from Chrome that provides an API for fetching spAs and generating pre-rendered content. The page-skeleton-webpack-plugin is a webpack plugin that allows Puppeteer to generate a skeleton page after the Puppeteer has generated the content. Take the official project generated by VUE-CLI3 as an example:

<div id="app"><! -- shell --></div>
Copy the code
const SkeletonPlugin = require('page-skeleton-webpack-plugin').SkeletonPlugin
const path = require('path')
module.exports = {
  publicPath: '/'.outputDir: 'dist'.configureWebpack: config= > {
    let plugins = []
    plugins.push(new SkeletonPlugin({
      pathname: path.resolve(__dirname, './shell'), // Pathname is the address to store the shell file
      staticDir: path.resolve(__dirname, './dist'), // Same as' output.path '
      routes: ['/'.'/about'].// Add the route that needs to generate the skeleton screen to the array
      port: '7890'
    }))
    config.plugins = [
      ...config.plugins,
      ...plugins
    ]
  },
  chainWebpack: config= > {
    if (process.env.NODE_ENV === 'production') {
      config.plugin('html').tap(opts= > {
        console.log(opts[0])
        opts[0].minify.removeComments = false
        return opts
      })
    }
  }
}
Copy the code

The following example is a simple configuration of the Page-skeleton-webpack-plugin. If you want to complete the configuration, you can go to Github to obtain it.

chainWebpack: config= > {
    if (process.env.NODE_ENV === 'production') {
      config.plugin('html').tap(opts= > {
        console.log(opts[0])
        opts[0].minify.removeComments = false
        return opts
      })
    }
  }
Copy the code

This is a modification of the HTML -webpack-plugin compression configuration integrated in VUe-cli3 to remove the removal comment, because the Page-skeleton-webpack-plugin relies on the annotation when compiling. However, the HTML -webpack-plugin integrated in VUE-cli3 will be compressed during compilation and the comments will be removed. Therefore, it should be configured separately, otherwise there will be no content under the GENERATED APP node during compilation. One more thing to be aware of when using vuE-cli3 scaffolding, if you are generating code, the runtime may report this error:

github

conclusion

This article briefly introduces the schemes I have tried in the practice of white screen optimization. Each scheme has its own advantages and disadvantages, and it is necessary to make a choice according to the actual business scenarios. I hope it will be helpful for you to solve such problems. If there is a mistake or not rigorous place, welcome criticism and correction, if you like, welcome to praise