This article focuses on pre-rendering front-end code using the prerender-SPa-plugin.

Pre-rendered (SSG) and server (SSR) rendering has certain difference, if you want to know, we can see: segmentfault.com/a/119000002… .

background

Because the previous website is developed using Vue, this front-end JavaScript rendering development mode is very unfriendly to search engines, there is no way to capture effective information. So in order to do SEO, we need to do some pre-rendering of the page.

Pre-render is best for static or small pages, rendering most of the content of the page in a single static rendering before deployment. So when the search engine is crawling, it can crawl to the relevant content information.

The status quo

At present, the official website of commercial enterprises is listed as follows:

  • The technical stack uses Vue, the scaffolding uses VUE-CLI, using JavaScript front-end rendering scheme (this scheme does not require the technical stack, compatible with all schemes)

  • The publishing tool uses the company’s tool. During the packaging process, HTML resources are transferred to domain NAME A, and CSS, JS, and Image resources are transferred to domain name B.

The target

The hope is to be able to pre-render a page that carries enough information when it is first visited without executing JavaScript to render the JavaScript content in advance into HTML.

The release is expected to be unmodified.

plan

Our solution is mainly implemented by prerender-SPa-plugin, a Webpack plug-in.

The main idea is to start the browser, grab the HTML after rendering, and then replace it.

We need to implement pre-render, so we need to do the following things:

  1. Plug-in introduction and configuration.

  2. Local authentication.

  3. Transform the packaging build process.

  4. Verify online.

Now, let’s talk about how we do this, one by one.

Plug-in introduction and configuration

First, we need to introduce a pre-render plugin and execute the following command:

mnpm i prerender-spa-plugin -D
Copy the code

This command, in addition to installing the plug-in itself, relies on puppeteer, which in turn relies on landing Chromium, so in the end we actually need to install chromium in the dependency.

If you install the puppeteer is very slow or frequently, you can refer to this document methods: brickyang. Making. IO / 2019/01/14 /… , specify puppeteer to download the image.

Once the installation is complete, we can add the corresponding configuration to the WebPack configuration file.

If you are also using vue-CLI, then the configuration we need to add is in vue.config.js. If we are directly modifying the webpack configuration, then the method is similar.

Here we take vue.config.js modification as an example:

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

module.exports = { ... .configureWebpack: {... .chainWebpack: config= > {
      config.plugin('prerender').use(PrerenderSPAPlugin, [
        {
          staticDir: path.join(__dirname, 'build'),
          routes: [
            '/'.'/product'.'/case'.'/about'.'/register',].renderer: new Renderer({
            headless: true.executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'.// Document.dispatchEvent (new Event('render- Event ')) in main.js.
            renderAfterDocumentEvent: 'render-event',})}]); }}}Copy the code

Because we use Webpack-chain in our project, our syntax is the chain-call-like method above. If you directly modify the words, is to use the original vUE configuration modification way.

Let me briefly introduce the meanings of some configurations above:

  • StaticDir: This refers to the directory where the pre-rendered files will be output.

  • Routes: This refers to routes that need to be pre-rendered. It is important to note that vue’s hash routing policy does not allow for pre-rendering, so if you want to pre-render, you need to change the route to history, and then after pre-rendering, it will become multiple HTML files, each with full routing function, but the default route is different.

  • Renderer: This is the puppeteer configuration that can be passed in. Let me mention some of the configurations I’ve used:

– headless: Indicates whether to use the headless rendering mode. You are advised to select true.

– executablePath: Specifies the path for Chromium (or Chrome). This configuration is required in TalOS, where the default chrome address is /usr/bin/google-chrome.

– renderAfterDocumentEvent: This means a pre-rendered capture after which event is triggered. This event needs to be triggered in the code itself using a dispatchEvent so that you can control the timing of the pre-rendering. The mounted hook of the outermost component is always triggered. If you have other requirements, you can specify it yourself.

See the plugin’s official documentation for more information.

Once the development is complete, we can build it locally and see if we can generate the code we expect.

Vue. Config. js indicates publicPath, causing prerendering failure

If you pass publicPath into vue.config.js to specify the third-party CDN domain name, CSS, JavaScript, Image and other resources will be transferred to different domain names, similar configuration is as follows:

module.exports = { ... .publicPath: `//awp-assets.cdn.net/${projectPath}`. };Copy the code

If there is no pre-rendering, this scheme will be uploaded to different CDN domains after packaging, and there is no problem accessing online.

However, at this time, the CSS and JS resources have not been uploaded to the CDN, and the browser cannot load the corresponding resources to render the page. In this case, the local pre-rendering will fail.

To solve this problem, there are two approaches.

  1. [Recommendation] Adjust the packaging strategy to upload non-HTML resources to the same CDN domain name, so that we can use the relative path to access these resources, and do not need to pass the new domain name to publicPath, so that we can access these values when building locally. This is a more reliable and reasonable method, more recommended.

  2. (If the above method is not feasible, consider this option.) Before pre-rendering, resources are locally accessible through relative paths. In this case, the url of the resource file in THE HTML is replaced with the url of the resource file, and then replaced back after pre-rendering. This method is a bit of a hack, but it does work after actual verification. To do this, write a simple WebPack plug-in of your own.

First, we need to install a new NPM package to replace the contents of the file (you can also write your own re, but this is more convenient) with the following command:

mnpm i replace-in-file
Copy the code

Once installed, we need to add two plugins for WebPack, one for afterEmit and one for Done. To understand why these two hook nodes are used, you can read the development section of the WebPack plugin.

const replace = require('replace-in-file');

let publicPath = `//awp-assets.cdn.net/${projectPath}`;

// The first replacement plug-in is to replace the path with the CDN domain name in the original packaging process with a relative path
function ReplacePathInHTMLPlugin1(cb) {
  this.apply = compiler= > {
    if (compiler.hooks && compiler.hooks.afterEmit) {
      compiler.hooks.afterEmit.tap('replace-url', cb); }}; }function replacePluginCallback1() {
  replace({
    files: path.join(__dirname, '/build/**/*.html'),
    from: new RegExp(
      publicPath.replace(/([./])/g.(match, p1) = > {
        return ` \ \${p1}`;
      }),
      'g'
    ),
    to: ' ',
  })
    .then(results= > {
      console.log('replace HTML static resources success', results);
    })
    .catch(e= > {
      console.log('replace HTML static resources fail', e);
    });
}

// The second replacement plugin replaces the relative path in the pre-rendered HTML file with the path with the CDN name
function ReplacePathInHTMLPlugin2(cb) {
  this.apply = compiler= > {
    if (compiler.hooks && compiler.hooks.done) {
      compiler.hooks.done.tap('replace-url', cb); }}; }function replacePluginCallback2() {
  replace({
    files: path.join(__dirname, '/build/**/*.html'),
    from: [/href="\/css/g./href="\/js/g./src="\/js/g./href="\/favicon.ico"/g].to: [
      `href="${publicPath}/css`.`href="${publicPath}/js`.`src="${publicPath}/js`.`href="${publicPath}/favicon.ico"`,
    ],
  })
    .then(results= > {
      console.log('replace HTML static resources success', results);
    })
    .catch(e= > {
      console.log('replace HTML static resources fail', e);
    });
}
Copy the code

The code above is the replacement plugin for the two WebPacks we need to add and the corresponding callback function. Now let’s see how to configure it in Webpack.

module.exports = {
  publicPath,
  outputDir,
  crossorigin: 'anonymous'.chainWebpack: config= > {
    config.plugin('replaceInHTML').use(new ReplacePathInHTMLPlugin1(replacePluginCallback));
    config.plugin('prerender').use(PrerenderSPAPlugin, [
      {
        staticDir: path.join(__dirname, 'build'),
        // We should only use the root path, since it is a hash route, there is no point in pre-rendering other pages, so no pre-rendering
        routes: ['/'].renderer: new Renderer({
          headless: true.executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'.// Document.dispatchEvent (new Event('render- Event ')) in main.js.
          renderAfterDocumentEvent: 'render-event',})}]); config.plugin('replaceInHTML2').use(new ReplacePathInHTMLPlugin2(replacePluginCallback2));
  }
Copy the code

Our first replacement plug-in needs to be performed before the pre-rendering plug-in. Before the pre-rendering plug-in is executed, the address of the resource in HTML is replaced with the relative path of the location. The second one needs to be executed after the replacement, which replaces the relative path in the pre-rendered backend resource with the CDN address.

With these two plug-ins, we can replace the path to complete the pre-render, and then complete the replacement guarantee line after the pre-render.

Local validation

Now that we have a pre-rendered HTML, we need to verify that the HTML meets our expectations.

A simpler way to verify this is to access the HTML file directly, or to start an HTTP static resource service.

To verify this, you can use curl to make the request. In this case, JavaScript does not execute and you can see what the HTML source file is.

FAQ

  1. Will render fail in earlier Versions of Chrome (e.g. V73)?

    This is because the chrome version is too low, resulting in pre-rendering failure. The solution is to upgrade the Chrome/Chromium version to the latest version (currently v93 is fine).

conclusion

If we need to implement SSG (static site generation), we can use prerender-spa-plugin, which can launch Chromium locally to grab THE HTML content and write it back to the HTML file. If we need to process the static resource file, We can use replacement plug-ins to replace the content before and after processing to achieve our demands.

While it may seem effective to replace compressed code directly, it is strongly recommended not to replace compressed files directly with script modifications, which are best handled in webPack’s Done hook callback.