Optimizing React apps: Hardcore edition
Tags: javascript, react, webpack, tutorial
Published: 2021-08-20 15:30:52

---
title: Optimizing React apps: Hardcore edition
published: true
description: React app optimization techniques you may have never heard of before
tags: javascript, react, webpack, tutorial
//cover_image: https://direct_url_to_image.jpg
---

You've heard of minifying. You've heard of lazy-loading. You've heard of tree shaking. You've done it all. Or did you? Here are some optimizations you may have never heard of before. Until now!

## Enable "loose" transformations in `@babel/preset-env`

Enabling ["loose" transformations](http://2ality.com/2015/12/babel6-loose-mode.html) may make your application considerably smaller. I **shaved off roughly 230.9 KB, or 16.2%** from my bundle!

This, however, comes at a price: your application may break, both when enabling, and disabling these transformations.

In my case, the only fix I needed to do was related to iterating over `HTMLCollection` (`document.querySelectorAll(…)`, `document.getElementsByTagName(…)` and `HTMLFormControlsCollection` (`form.elements`). I was no longer able to do e.g. `[...form.elements]`, I had to swap it out for `Array.from(form.elements)`.

Still tempted by the large savings? Give it a go by enabling `loose` flag in Babel config:

`babel.config.json`
```diff
  "presets": [
-   "@babel/preset-env"
+   ["@babel/preset-env", {
+     "loose": true
+   }]
  ]
```

## Remove prop-types from your production bundle

PropTypes are incredibly helpful during development, but they are of no use for your users. You can use `babel-plugin-transform-react-remove-prop-types` to remove PropTypes from your bundle.

To install, run:

```
npm install --save-dev babel-plugin-transform-react-remove-prop-types
```

or

```
yarn add -D babel-plugin-transform-react-remove-prop-types
```

and add it to your Babel config like so:

`babel.config.json`
```diff
  "env": {
    "production": {
      "plugins": [
+        "transform-react-remove-prop-types"
      ]
    }
  }
```

Savings will vary depending on the size of your app. In my case, **I shaved off 16.5 KB or about 1.2%** from my bundle.

### Consider `unsafe-wrap` mode

`unsafe-wrap` mode is, as the name states, a bit *unsafe* for the reasons [well explained in plugin's docs](https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types#options).

However, in my case, PropTypes were not accessed from anywhere and the application worked flawlessly.

To enable this mode, you need to change your Babel config like so:

`babel.config.json`
```diff
  "env": {
    "production": {
      "plugins": [
-       "transform-react-remove-prop-types"
+       ["transform-react-remove-prop-types", {
+         "mode": "unsafe-wrap"
+       }]
      ]
    }
  }
```

This way, **I shaved off a total of 35.9 KB or about 2.5%** from my bundle.

## Enable new JSX transform

Enabling new JSX transform will change the way Babel React preset transpiles JSX to pure JavaScript. 

I explained the benefits of enabling it in my other article: [How to enable new JSX transform in React 17?](https://dev.to/wojtekmaj/how-to-enable-automatic-runtime-in-react-17-with-babel-preset-react-52l). 

I highly recommend you to have a read. If that's TL;DR though, all you need to do for quick results is make sure that `@babel/core` and `@babel/preset-env` in your project are both on version `7.9.0` or newer, and change your Babel config like so:

`babel.config.json`
```diff
  "presets": [
-   "@babel/preset-react"
+   ["@babel/preset-react", {
+     "runtime": "automatic"
+   }]
  ]
```

And poof! Roughly **10.5 KB, or 0.7%** of my bundle was gone.

## Minify your HTML

Chances are your bundler is smart enough to minify JavaScript by default when ran in production mode. But did you know you can minify HTML, too? And JavaScript *in that HTML* as well?

You're in? Great! Here's what you need to do:

Install `html-minifier-terser`:

```
npm install --save-dev html-minifier-terser
```

or

```
yarn add -D html-minifier-terser
```

and change your Webpack config to use it. Define minifier options:

`webpack.config.js`
```js
const minifyOptions = {
  // Defaults used by HtmlWebpackPlugin
  collapseWhitespace: true,
  removeComments: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  useShortDoctype: true,
  // Custom
  minifyCSS: true,
  minifyJS: true,
};
```

and use them in `HtmlWebpackPlugin`…:

`webpack.config.js`
```diff
    new HtmlWebpackPlugin({
+     minify: minifyOptions,
      template: 'index.html',
    }),
```

…as well as in `CopyWebpackPlugin`:

`webpack.config.js`
```js
const { minify } = require('html-minifier-terser');
```

`webpack.config.js`
```diff
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'index.html',
          to: '',
+         transform(content) {
+           return minify(content.toString(), minifyOptions);
+         },
        },
      ]
    }),
  ],
```

## Use `babel-plugin-styled-components` (`styled-components` users only)

If you use `styled-components`, make sure to use [their Babel plugin](https://styled-components.com/docs/tooling#babel-plugin), too. Not only it adds minification of styles, but also adds support for server-side rendering, and provides with a nicer debugging experience.

To install, run:

```
npm install --save-dev babel-plugin-styled-components
```

or

```
yarn add -D babel-plugin-styled-components
```

and add it to your Babel config like so:

`babel.config.json`
```diff
  "env": {
    "production": {
      "plugins": [
+        "styled-components"
      ]
    }
  }
```

This will shave off a few kilobytes on its own, but due to added `displayName`s the savings will not be so apparent just yet. So now…

### Disable `displayName` in production builds

`babel.config.json`
```diff
  "env": {
    "production": {
      "plugins": [
+       ["styled-components", {
+         "displayName": false,
+       }]
      ]
    }
  }
```

Doing so in my app gave me a **surprisingly large savings of 50.4 KB or 3.5%**.

## Wrap `createGlobalStyle` contents in `css` (`styled-components` users only)

Apparently, while `babel-plugin-styled-components` is capable of minifying styles, it doesn't minify anything within `createGlobalStyle`. So, chances are you're shipping your app with tons of unnecessary whitespace.

Remove them by simply wrapping `createGlobalStyle` contents in `css` as well, like so:

```diff
-const GlobalStyle = createGlobalStyle`
+const GlobalStyle = createGlobalStyle`${css`
   // Your global style goes here
-`;
+`}`;
```

## Replace `react-lifecycles-compat` with an empty mock

`react-lifecycles-compat` is a dependency that polyfills lifecycle methods introduced in React 16.3 so that the components polyfilled would work with older React versions. Some dependencies may still use this polyfill in order not to break older React version support.

If you use React 16.3 or newer, you don't need `react-lifecycles-compat`. You can replace it with a mocked version like so:

`__mocks__/reactLifecyclesCompatMock.js`
```js
module.exports = {
  polyfill: (Component) => Component,
};
```

`webpack.config.js`
```diff
  resolve: {
    alias: {
+     'react-lifecycles-compat': path.resolve(__dirname, '__mocks__', 'reactLifecyclesCompatMock.js'),
    },
  },
```

Doing so **will save you 2.5 KB**.

## Replace `classnames` with `clsx`

`classnames` is not a large dependency, only 729 bytes, but `clsx` is fully compatible with `classnames` at just 516 bytes. So, replacing `classnames` with `clsx` in your app **will save you 213 bytes**.

Chances are you'll have *both* `classnames` and `clsx` in your app, e.g. because dependencies may require one or the other. In this case, you can use Webpack's `alias` to get rid of `classnames` from your bundle:

`webpack.config.js`
```diff
  resolve: {
    alias: {
+     classnames: 'clsx',
    },
  },
```

Doing so **will save you 729 bytes**.

## Missing something?

Please share your ideas for not-so-obvious optimizations in the comments below!