Fast Inline Images With React and Webpack
Tags: webpack, react, webperf, performance
Published: 2022-06-07 16:04:52

---
title: Fast Inline Images With React and Webpack
published: true
date: 2022-06-07 08:00:00 UTC
tags: webpack, react, webperf, performance
canonical_url: https://requestmetrics.com/web-performance/fast-inline-images-react-webpack
cover_image: https://requestmetrics.com/assets/images/webperf/inline-images/inline-images-webpack-2000.png
---

[Webpack](https://webpack.js.org/) is great for building React applications, but did you know it can help you optimize app performance too? Webpack can automatically inline image data, improving performance by reducing the number of requests your page needs to make. Let’s learn how.

## Image Inlining

Normally, every image on a webpage is a unique file that the browser must make a HTTP request to display. The more requests the browser needs to make, the longer page loading will take. Conversely, reducing the total number of requests will generally improve performance.

Image inlining reduces the number of additional requests needed for page load by embedding image data directly into the HTML or Javascript bundle. As with anything, this optimization doesn’t come for free: The total number of image requests is reduced at the price of a larger initial payload. This results in a performance sweet spot where small images are inlined, but larger images are loaded normally with an additional HTTP requests.

Hey! Don’t want to read all the in’s and outs of bending Webpack into shape? [Jump to the final webpack configuration.](#tldr-final-webpack-configuration)

## A Simple React App

To test image inlining we’ve created a simple React app:

![Files in a base React app](https://requestmetrics.com/assets/images/webperf/inline-images/basic-react-app-file-listing.png)
_Basic React App Source Directory_

A `index.html` file is used to bootstrap the single (compiled) JSX file:

```html
<html>
    <header>
        <title>React With Inline Images</title>
    </header>
    <body>
        <div class="images-container"></div>
    </body>
    <script src="index.js"></script>
</html>
```

```javascript
import React from "react"
import ReactDOM from "react-dom/client"

// Just to convince webpack to copy the file
import indexHtml from "./index.html"

function SomeImages(props) {
    return (
        <div>
            <h2>{props.title}</h2>
            <p>
                <h3>Some small images:</h3>
                <img src="images/small-bee.png" />
                <img src="images/small-chick.png" />
                <img src="images/small-puppy.png" />
                <img src="images/small-tree.png" />
            </p>
            <p>
                <h3>Some larger images:</h3>
                <img src="images/medium-duckling.jpg" /><br />
                <img src="images/medium-squirrel.jpg" />
            </p>
        </div>
    )
}

var containerDiv = document.querySelector(".images-container");
var root = ReactDOM.createRoot(containerDiv);
root.render(SomeImages({ title: "React with Inline Images" }));
```

## Building React JSX Files with Webpack

First, Webpack and React dependencies need to be installed with NPM:

```bash
npm install react react-dom
npm install --save-dev webpack webpack-cli babel-loader @babel/preset-react
```

Webpack doesn’t compile JSX out of the box. Adding a module rule to `webpack.config.js` tells Webpack to use Babel when compiling JSX files. There is an additional rule for copying our bootstrap html to the output folder. More on “asset modules” later:

```javascript
var path = require("path");

module.exports = {
    mode: "development",
    entry: "./src/index.jsx",
    output: {
        filename: "index.js",
        path: path.resolve("dist/"),
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: "babel-loader",
                options: {
                    "presets": ["@babel/preset-react"]
                }
            },
            {
                test: /\.html$/i,
                type: "asset/resource",
                generator: {
                    filename: "[name][ext]"
                }
            }
        ]
    }
};
```

Running `webpack` from the command line compiles our JSX into an output folder named `dist/`, but there are some issues that need to be fixed.

### Importing/Requiring Images

Well, things ALMOST work. All our image tags are broken when we load the compiled app:

![Webpack Images Not Working](https://requestmetrics.com/assets/images/webperf/inline-images/broken-images.png)

And no images were output to our `dist/` folder:

![No images in Webpack output directory](https://requestmetrics.com/assets/images/webperf/inline-images/dist-dir-1.png)

Images aren’t displaying because Webpack doesn’t read the urls in `src` attributes. None of our image files are copied to the `dist/` folder because Webpack assumed we are referencing an external dependency that it doesn’t need to worry about. The JSX needs to import or require the images so that Webpack knows we need those images:

```jsx
// BEFORE:
<p>
    <h3>Some small images:</h3>
    <img src="images/small-bee.png" />
    <img src="images/small-chick.png" />
    <img src="images/small-puppy.png" />
    <img src="images/small-tree.png" />
</p>
<p>
    <h3>Some larger images:</h3>
    <img src="images/medium-duckling.jpg" /><br />
    <img src="images/medium-squirrel.jpg" />
</p>

// AFTER:
<p>
    <h3>Some small images:</h3>
    <img src={require("./images/small-bee.png")} />
    <img src={require("./images/small-chick.png")} />
    <img src={require("./images/small-puppy.png")} />
    <img src={require("./images/small-tree.png")} />
</p>
<p>
    <h3>Some larger images:</h3>
    <img src={require("./images/medium-duckling.jpg")} /><br />
    <img src={require("./images/medium-squirrel.jpg")} />
</p>
```

### Using Asset Modules For Image Files

And, things are still broken. Webpack knows about our images now, but is throwing errors:

```bash
ERROR in ./src/images/medium-duckling.jpg 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, /
    currently no loaders are configured to process this file. /
    See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
 @ ./src/index.jsx 16:9-48
```

Webpack is failing because it doesn’t know what to do with our image files. Just as with JSX, we need a module rule telling Webpack what to do when it encounters an image.

Webpack 5 has a new feature called [Asset Modules](https://webpack.js.org/guides/asset-modules/) which is meant to replace the `url-loader`, `file-loader`, and `raw-loader`’s used in Webpack 4 for this situation. Just to get things working, we’ll tell Webpack to always copy image files to the output folder:

```javascript
// Added to webpack.config.js:
module: {
    rules: [
        // ...snip...
        {
            test: /\.(png|jpg)$/i,
            type: 'asset/resource'
        }
    ]
}
```

Finally, webpack is including images in the compiled output:

![Webpack output directory with images](https://requestmetrics.com/assets/images/webperf/inline-images/dist-dir-2.png)

And our page is working:

![React app with images](https://requestmetrics.com/assets/images/webperf/inline-images/working-images.jpg)

### Place All Images in Their Own Folder

Webpack is copying our image files, but all the images are in the root of the output directory with un-intelligible hashes for names. Any more images and the `dist/` folder is going to be a mess. We can tell the asset module to name our images better and place them in their own folder:

```json
{
    test: /\.(png|jpg)$/i,
    type: 'asset/resource'
    // Added:
    generator: {
        filename: 'images/[name]-[hash][ext]'
    }
}
```

Now images are all in a separate folder with understandable names. Keeping the hash helps with cache busting:

![Webpack output directory with all images in their own folder](https://requestmetrics.com/assets/images/webperf/inline-images/dist-dir-3.png)

### Automatically Clean Webpack’s Output Directory

Why is my dist directory so cluttered? As the webpack configuration has changed, we’ve manually cleaned up old files from the `dist/` directory. By default, Webpack never removes old files that are no longer neeed. We can configure Webpack to automatically clean the dist folder each build:

```json
output: {
    // ...snip...
    clean: true
},
```

## Inline Small Images

Finally, images are working and we can do what we came here for: inline small images! Webpack’s base `asset` Asset Module automatically handles inlining for us. Webpack will inline anything under 8KB by default, but we can also explicitly set the size threshold. Images over the limit will be output into the `dist/` folder as they were previously:

```json
module: {
        rules: [
            // ...snip...
            {
                test: /\.(png|jpg)$/i,
                // Previously we had disabled inlining by using 'asset/resource'
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024 // Inline images under 10KB
                    }
                },
                generator: {
                    filename: 'images/[name]-[hash][ext]'
                }
            }
        ]
    }
```

The smaller images are inlined and the output directory only contains larger images:

![Webpack output directory with only large images](https://requestmetrics.com/assets/images/webperf/inline-images/dist-dir-4.png)

We can see the Base64 encoded images if we inspect the rendered page:

![Base64 images in Chrome Dev Tools inspector](https://requestmetrics.com/assets/images/webperf/inline-images/base64-encoded-images-in-debugger.png)

## TLDR: Final Webpack Configuration

Now we’ve got Webpack automatically inlining images along with a few quality of life improvements. With everything working, our webpack configuration looks like this:

```javascript
var path = require("path");

module.exports = {
    mode: "development",
    entry: "./src/index.jsx",
    output: {
        filename: "index.js",
        path: path.resolve("dist/"),
        clean: true
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: "babel-loader",
                options: {
                    "presets": ["@babel/preset-react"]
                }
            },
            {
                test: /\.html$/i,
                type: "asset/resource",
                generator: {
                    filename: "[name][ext]"
                }
            },
            {
                test: /\.(png|jpg)$/i,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024 // Inline images under 10KB
                    }
                },
                generator: {
                    filename: 'images/[name]-[hash][ext]'
                }
            }
        ]
    }
};
```

## Conclusion

We successfully convinced Webpack to automatically inline our images. This reduced the number of network requests our page needs to make, but did it make our page faster? That’s the kind of question Request Metrics was built to answer. [Try it out](https://app.requestmetrics.com/signup) today to measure how your site performs for real users in production.

We’ve covered just one way to optimize images here, but there are many other ways to [optimize image performance](https://requestmetrics.com/web-performance/high-performance-images).