7. Oct 2021
FrontendOptimizing 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.
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) |
JPEG | imagemin-mozjpeg | imagemin-jpegtran |
PNG | imagemin-pngquant | imagemin-optipng |
GIF | imagemin-giflossy | imagemin-gifsicle |
SVG | imagemin-svgo | |
WebP | imagemin-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.