9. Nov 2021Frontend

Optimizing React SPA applications in benchmark tools vol II.

I bring you the second part of tips on how to optimize your SPA (single page apps) based on React framework. To help improve performance, and so improve scores in benchmarking tools. In this episode we will look at dynamic imports, removing unused code, refactoring or Nginx Pagespeed

Ján CvenčekFrontend Develooper

The tips below have been applied to a React application created with the CRA (Create React App), which uses webpack v4. CRA is preconfigured with several techniques for better application performance and speed. To modify the webpack configuration in CRA without executing the eject command, 
it is necessary to use libraries like craco or customize-cra. The production build of the application is served using the Nginx web server.

As with the first part, where I mainly covered file compression, web fonts 
and CSS, I used following tools to measure performance and speed scores:

The tools themselves in the outputs suggest solutions to eliminate individual problems.

The following procedures are not the only ones, and several of them have alternative ways to resolve reported problems from the tools.

Dynamic imports

Dynamic import of modules allows the corresponding * .chunk.js file 
to be downloaded by the browser only when it is needed. The result is:

  • smaller size of the resulting bundle
  • more .js files called "chunks" - a module covering comprehensive functionality
  • faster page loading - downloaded only what is needed

Webpack v4 supports dynamic imports using SplitChunksPlugin. CRA also supports code splitting. Imports can be named / change loading strategy / prioritize load etc. via parameters written as comments.

React.lazy

Allows lazy loading of components to be rendered on a given route. It is possible to modify them with parameters through the mentioned comments.

Example of import

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
// named chunk => AnotherComponent.<hash>.chunk.js
const AnotherComponent = React.lazy(() => import(/* webpackChunkName: "AnotherComponent" */ './AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Removal of unused code, reduction of bundle size

To find an unused code / assets, it is possible to use e.g. deadfile tool.

The following are available for analyzing the size of individual JS bundles:

According to the output, try to shrink large bundles e.g. reactjs, lodash, momentjs, chartjs… If there is a suitable alternative (preact, lodash-es, just,…) with a smaller size → replace it. The method of import can also help, 
where instead of importing the whole module, only the required functionality 
is imported, e.g.

import {join} from 'lodash' → import join from 'lodash/join'

⚠The priority is to keep the functionality ahead of the smaller bundle size!⚠

Lazy load of the elements

There are several libraries that help with lazy loading elements like <img>, <picture>, iframe, video, audio, etc. They also help with allocating space 
in the layout for non-rendered elements, with responsiveness, dragging content into scroll containers as needed, etc. Their implementation is at the developer's discretion regarding the performance benefits for the application. If you need 
to implement the following functionalities, it is possible to use:

Refactoring

For a better result in audit tools, it is often necessary to refactor the application / part of it itself. The problem areas in the application are most easily found 
by a programmer who knows the application in depth. The following links can help you refactor and find problem areas in the application:

Nginx Pagespeed

PageSpeed is an open-source server module for Nginx and Apache servers from Google that helps optimize the web. The module implements many performance recommendations from Google PageSpeed Insights.

Improves website latency by changing the resources on this page to match best practices in terms of site performance.

  • Image optimalization - compression, translation into WebP (for supported browsers), image resizing
  • Minimizes HTML
  • Minimizes and merges CSS, JavaScript
  • Optimizes file caching
  • Combines external JavaScript and optimize it
  • Optimizes file loading

Configuration example

server {

listen 443;

…

include /etc/nginx/pagespeed.conf;
}

 

/etc/nginx/pagespeed.conf

pagespeed on;

pagespeed FileCachePath "/var/cache/ngx_pagespeed/";

pagespeed FileCacheSizeKb 102400;

pagespeed FileCacheCleanIntervalMs 3600000;

pagespeed FileCacheInodeLimit 50000;

pagespeed EnableCachePurge on;

pagespeed PurgeMethod PURGE;

pagespeed RewriteLevel CoreFilters;

## Text / HTML
pagespeed EnableFilters combine_heads;
pagespeed EnableFilters collapse_whitespace;
pagespeed EnableFilters convert_meta_tags;
pagespeed EnableFilters elide_attributes;
pagespeed EnableFilters pedantic;
pagespeed EnableFilters remove_comments;
pagespeed EnableFilters remove_quotes;
pagespeed EnableFilters trim_urls;

## JavaScript
pagespeed EnableFilters combine_javascript;
pagespeed EnableFilters canonicalize_javascript_libraries;
pagespeed EnableFilters inline_javascript;

## CSS
pagespeed EnableFilters outline_css;
pagespeed EnableFilters combine_css;
pagespeed EnableFilters inline_import_to_link;
pagespeed EnableFilters inline_css;
pagespeed EnableFilters inline_google_font_css;
pagespeed EnableFilters move_css_above_scripts;
pagespeed EnableFilters move_css_to_head;
pagespeed EnableFilters prioritize_critical_css;
pagespeed EnableFilters rewrite_css;
pagespeed EnableFilters fallback_rewrite_css_urls;
pagespeed EnableFilters rewrite_style_attributes_with_url;

## Images
pagespeed EnableFilters dedup_inlined_images;
pagespeed EnableFilters inline_preview_images;
pagespeed EnableFilters resize_mobile_images;
pagespeed EnableFilters lazyload_images;
pagespeed EnableFilters inline_images;
pagespeed EnableFilters convert_gif_to_png;
pagespeed EnableFilters convert_jpeg_to_progressive;
pagespeed EnableFilters recompress_jpeg;
pagespeed EnableFilters recompress_png;
pagespeed EnableFilters recompress_webp;
pagespeed EnableFilters strip_image_color_profile;
pagespeed EnableFilters strip_image_meta_data;
pagespeed EnableFilters jpeg_subsampling;
pagespeed EnableFilters convert_png_to_jpeg;
pagespeed EnableFilters resize_images;
pagespeed EnableFilters resize_rendered_image_dimensions;
pagespeed EnableFilters convert_jpeg_to_webp;
pagespeed EnableFilters convert_to_webp_lossless;
pagespeed EnableFilters insert_image_dimensions;
pagespeed NoTransformOptimizedImages on;
pagespeed EnableFilters sprite_images;

pagespeed FetchHttps enable,allow_self_signed;

location ~ ".pagespeed.([a-z].)?[a-z]{2}.[^.]{10}.[^.]+" {
    add_header "" "";
}
location ~ "^/pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
location ~ "^/ngx_pagespeed_beacon$ps_dollar" { }

The configuration was set to Docker image hitko / nginx-pagespeed with brotli compression

Ján CvenčekFrontend Develooper