<aside> ๐Ÿง‘โ€๐ŸŽ“ The critical request chain is necessary to learn what chunks must be preload. Learn more how to identify it:

How to identify critical request chain?

</aside>

To preload critical chunks, means to add preload links for chunks necessary to render each page. Consider the following request chain (unoptimized):2

Untitled

Preloading chunks will have the following effect:

Untitled

To achieve this effect, following actions are required:

1. Lookup critical named chunk groups

<aside> ๐Ÿง  Webpack comes pre-loaded with Magic Comments feature. It allows hinting Webpack how to name the chunk group and what strategy use to load it. It looks as follows:

import(
    /* webpackMode: "lazy", webpackChunkName: "cms" */
    './CmsPage.component'
);

</aside>

Lookup the critical request chains (i.e. for homepage, PLP, PDP), note the chunks loading for each page. For example, these could be: cms, product, category chunks (based on optimized critical request chains).

2. Create a new build configuration plugin

Create a next extension, declare a build configuration plugin inside. It is possible to use an exiting extension too. Follow these instructions:

How to build configuration plugins?

3. Create a Webpack plugin to generate preload chunks config

Create a new file preload.js along the build configuration plugin, with following content (do not forget cacheGroupWhitelist with desired names from step 1):

<aside> ๐Ÿง  By adding new entries to cacheGroupWhitelist the named chunk group will be added to a list of pre-loaded ones. Add chunk groups

</aside>

const path = require('path');

class PreloadPlugin {
  addPreloadConfig(compilation, htmlPluginData) {
    const stats = compilation.getStats().toJson({ all: false, chunkGroups: true });
		// vvv Update with desired cache groups from step 1
    const cacheGroupWhitelist = ['cms', 'product', 'category'];
    const localeCacheGroup = Object.keys(stats.namedChunkGroups).filter((cacheGroup) => /[a-z]{2}_[A-Z]{2}/.test(cacheGroup));

    const preloadData = [
      ...cacheGroupWhitelist,
      ...localeCacheGroup
    ].reduce((acc, cacheGroup) => {
      return {
        ...acc,
        [cacheGroup]: stats.namedChunkGroups[cacheGroup]?.assets.map(
          (asset) => process.env.PUBLIC_URL
            ? path.join(process.env.PUBLIC_URL, asset)
            : `/${asset}`
        )
      };
    }, {});

    const scriptHtml = `<script>window.preloadData = ${JSON.stringify(preloadData)};</script>`;
    htmlPluginData.html = htmlPluginData.html.replace('<head>', `<head>${scriptHtml}`);
    return htmlPluginData;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(
      this.constructor.name,
      compilation => {
        // This is set in html-webpack-plugin pre-v4.
        let hook = compilation.hooks.htmlWebpackPluginAfterHtmlProcessing;

        if (!hook) {
          const [HtmlWebpackPlugin] = compiler.options.plugins.filter(
            (plugin) => plugin.constructor.name === 'HtmlWebpackPlugin');
          console.log('Unable to find an instance of HtmlWebpackPlugin in the current compilation.');
          hook = HtmlWebpackPlugin.constructor.getHooks(compilation).beforeEmit;
        }

        hook.tapAsync(
          this.constructor.name,
          (htmlPluginData, callback) => {
            try {
              callback(null, this.addPreloadConfig(compilation, htmlPluginData));
            } catch (error) {
              callback(error);
            }
          }
        );
      }
    );
  }
}

module.exports = { PreloadPlugin };

<aside> ๐Ÿง  This plugin uses Webpack stats to retrieve information about files included in specified named chunk groups. It then inlines this information right after the head element into the HTML document template.

</aside>

Inject the plugin into Webpack configuration, inside of a build configuration plugin:

webpackConfig.plugins.push(new PreloadPlugin());

4. Add logic to generate preload links (based on preload config) in HTML document

The template used to generate HTML document on server side, is located in public folder, in Magento theme mode, the public/index.php template is used. Append the following code to HTML document after the script tag that declares window.actionName (we need itโ€™s value to determine current page):

<aside> ๐Ÿง  By appending new entries to chunkValidator the control over preloaded chunks can be obtained. Use it to define conditions for chunk groups to preload.

</aside>

<script>
  const chunkValidator = {
		// vvv Preload pages conditionally
    category: window.actionName.type === 'CATEGORY',
    cms: window.actionName.type === 'CMS_PAGE',
    product: window.actionName.type === 'PRODUCT',
		// vvv Always preload current locale
    [window.defaultLocale]: true
  };

  const appendPreloadLink = (chunk) => {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'script';
    link.href = chunk;
    document.head.appendChild(link);
  }

  if (window.preloadData) {
    Object.entries(window.preloadData).forEach(([key, chunks]) => {
      if (chunkValidator[key]) {
        chunks.forEach((c) => appendPreloadLink(c));
      }
    });
  }
</script>

<aside> ๐ŸŽ‰ Thatโ€™s how you preload chunks!

</aside>