7. Oct 2021Frontend

Optimizing React SPA applications in benchmark tools

In this article, I will present solutions that can help with the optimization of SPA (single page app), primarily based on the React framework. Applying each procedure helps speed up loading, improve performance, and get higher scores in benchmark tools. In the first part I will focus on file compression, web fonts and CSS.

Ján CvenčekFrontend Develooper

The tips below have been applied to a React application created with the Create React App (CRA), 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.

The following tools were used 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 way to solve the reported problem from the tools, several of them have an alternative.

Compression

The compression of the files served by the web server can be applied either on the server side or statically during the build process.

Compress-create-react-app

For static compression, the compress-create-react-app library can be used as a build script via npm. Performs gzip and brotli compression for html, css and js files. In case the compression is not sufficient, or its configuration is not good, it is possible to use one of the procedures below.

Gzip

The compression method is less CPU intensive, but the size of the compressed files is larger compared to Brotli. The individual methods can be extended with additional switches / parameters as required.

Static compression

It can be done using the system utility gzip (needs to be installed for Windows) gzip -r -f -k Since the utility is not cross- platform pre-installed , it is better to use compression-webpack-plugin.

Live compression - Nginx

Enables in the Nginx configuration.

server {
  listen  <port>
  gzip on;
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_types
  text/plain
  text/css
  text/xml
  application/json
  application/javascript
  application/xml+rss
  application/atom+xml
  application/xhtml+xml
  application/xml
  image/svg+xml;
  ...
}

 

Brotli

A more powerful method compared to gzip at the loss of CPU power.

⚠Supported just over HTTPS

Static compression

Using compression-webpack-plugin

const CompressionPlugin = require('compression-webpack-plugin')
...
new CompressionPlugin({
	filename: '[path][base].br',
	algorithm: 'brotliCompress',
	test: /\.(js|css|html|svg)$/,
	compressionOptions: {
		level: 11
	},
	threshold: 10240,
	minRatio: 0.8
})

 

Live compression - Nginx

It is enabled in the configuration for Nginx, which must have the Brotli module installed. For Docker it is possible to use e.g. fholzer/nginx-brotli

‍server {
  listen  <port>
  brotli on;
  brotli_comp_level 7;
  brotli_types
  text/xml
  text/plain
  text/javascript
  text/css
  image/svg+xml
  image/x-icon
  image/x-win-bitmap
  image/vnd.microsoft.icon
  font/eot
  font/otf
  font/opentype
  application/x-font-opentype
  application/json
  application/x-font-ttf
  application/vnd.ms-fontobject
  application/javascript
  application/xml
  application/xhtml+xml
  application/x-javascript
  application/x-font-truetype
  application/xml+rss;
  ...
}

Imagemin

Image compression tool. There are several plugins for different formats. Each has several parameters for setting up the plugin as needed. Individual plugins with links are in the table. Supports lossless (image quality after compression matches the original) or lossy compression. Lossless compression is more efficient in terms of size savings at the expense of quality.

Format   Lossy Plugin(s)Lossless Plugin(s)
JPEGimagemin-mozjpegimagemin-jpegtran
PNGimagemin-pngquantimagemin-optipng
GIFimagemin-giflossyimagemin-gifsicle
SVGimagemin-svgo 
WebPimagemin-webp 

Example of a PNG compression via GULP

npm install gulp gulp-imagemin --save-dev

Lossless compression (imagemin-optipng)

npm install imagemin-pngquant --save-dev

 

import gulp from 'gulp'
import imagemin from 'gulp-imagemin'
import optipng from 'imagemin-optipng'

gulp.task('optimize-png-images', () => {
	return gulp.src('./build/static/media/*.png')
		.pipe(imagemin([
			optipng({optimizationLevel: 7})
		]))
		.pipe(gulp.dest('build/static/media/'))
})

 

Lossy compression (imagemin-optipng)

npm install imagemin-optipng--save-dev

import gulp from 'gulp'
import imagemin from 'gulp-imagemin'
import optipng from 'imagemin-optipng'

gulp.task('optimize-png-images', () => {
	return gulp.src('./build/static/media/*.png')
		.pipe(imagemin([
			optipng({optimizationLevel: 7})
		]))
		.pipe(gulp.dest('build/static/media/'))
})

Webfonts

Make sure the text is visible while loading web fonts. Fonts are often large files that take a while to load. Some browsers hide text until the font is loaded, which causes a flash of invisible text (FOIT) - Lighthouse metric.

Solution – add the font-display: swap; attribute to the @ font-face declaration

@font-face {
    font-family: 'Polo';
    src: url('external_assets/fonts/PoloR-Regular.eot');
    src: url('external_assets/fonts/PoloR-Regular.eot?#iefix') format('embedded-opentype'), url('external_assets/fonts/PoloR-Regular.woff2') format('woff2'), url('external_assets/fonts/PoloR-Regular.woff') format('woff'), url('external_assets/fonts/PoloR-Regular.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

CSS

Before viewing the page, the browser must download and parse the CSS files, which makes the CSS a rendering-blocking resource. If the CSS files are large or the network conditions are bad, the requirements for the CSS files can significantly increase the time it takes to render the web page.

If the Lighthouse report reports a First Contentful Paint (FCP) problem - "Eliminate render-blocking resource" it is advisable to try the extraction of critical (they are currently needed for display) and non-critical CSS styles. After optimization, only critical styles are loaded synchronously, while non-critical ones are loaded in a non-blocking manner.

Possible solution- html-critical-webpack-plugin

The plugin uses the puppeteer library with the Chromium/Chrome headless browser - you need to make sure that the environment where the PROD build will run has the necessary dependencies installe

html-critical-webpack-plugin parses critical CSS styles and pulls them into a <style></style> in the <head> tag in HTML, eliminating the need to make another request to load them.

ℹ It is recommended to use the plugin only during the production build

 

if (process.env.NODE_ENV == 'production') {
	config.plugins = [
		...config.plugins,
		new HtmlCriticalWebpackPlugin({
			base: path.resolve(__dirname, 'build'),
			src: 'index.html',
			dest: 'index.html',
			inline: true,
			minify: true,
			extract: true,
			width: 320,
			height: 565,
			penthouse: {
				blockJSRequests: false
			},
		})
	]
}

 

More optimization tips can be found in the second part, where you will learn how to do dynamic imports, refactoring or removing unused code.

Ján CvenčekFrontend Develooper