• Mail us
  • Book a Meeting
  • Call us
  • Chat with us

ReactJS

Reducing Your React Bundle Size: A Detailed Guide to Faster Apps


Overview

As frontend applications grow in complexity, developers often encounter a common issue: increasing bundle sizes. While initially a new React app might feel fast with quick production builds, over time, these can start to drag, leading to slower applications and unhappy users. According to the source, 99% of the time, this slowdown is due to an uncontrolled increase in bundle size.

This article, drawing from practical examples and proven techniques, will guide you through 7 effective ways to reduce your React bundle size and speed up your builds. These methods are demonstrated in a real React application available on GitHub, allowing you to follow along and see the impact of each optimization. By applying these simple yet powerful tips, you can achieve significant reductions in your bundle size, as demonstrated by the example which went from 283.39 kB to 198.33 kB, a reduction of over 30%.

The Setup

The techniques discussed are based on a React application generated with V0. The source provides a GitHub repository where each optimization step is implemented in a dedicated branch, building upon the previous ones. The main branch contains the unoptimized version, while the step-7-lazy-load-components branch holds the fully optimized code. To analyze the bundle contents, the author used vite-bundle-analyzer. It's worth noting that global CSS was used in the demo for speed, but in real applications, using CSS Modules or tools like Tailwind is generally preferred.

Step 1: Eliminate Side Effects in Files

Bundlers like Webpack rely on tree shaking to exclude unused code from the final bundle. However, this process is hindered when a file contains side effects, such as modifying the window object. If a file has side effects, the bundler is forced to include it in the bundle, even if none of its exports are actually used.

  • Example: In the demo app, the HelloWorld.js file had a side effect: window.someBadSideEffect = "I'm a side effect and I will be included inside the bundle even if not used";. Despite not being used anywhere in the application, this file's code was present in the final bundle.
  • Fix: The solution is simple: remove the side effect. Once the side effect was removed, the bundler could correctly identify that the file was unused and exclude it from the bundle.

Step 2: Hunt Down Unused Files & Packages

While unused files and packages might not always directly inflate your bundle size, they can still negatively impact your project:

  • Slow down bundling: The bundler has to process more files, increasing build times.
  • Risk breaking tree-shaking: If an unused file or package contains side effects, it can still force the bundler to include related code.

Tool Recommendation: To identify dead code, the source recommends using tools like npx knip or npx depcheck.

In the demo: Running these tools flagged HelloWorld.js and the entire lodash-es library as unused. Removing these resulted in a decrease in the number of modules processed from 44 to 42. The author notes that the impact of removing unused dependencies will be more significant in larger, more complex applications, further accelerating build times.

Step 3: Avoid Barrel Files

Barrel files are modules that re-export multiple other modules, often found in index.js files within component directories. While they can lead to cleaner and more concise imports:

import { Dashboard, UserManagement, Settings, Clock } from "./components"; 

They also introduce significant downsides in terms of bundle optimization:

  • Side effects propagate: If any module re-exported by the barrel file has side effects, the bundler might include the entire barrel file and potentially other unrelated modules within it. This is why the side effects in Step #1 had a broader impact.

  • More files to process: The bundler has to process all the modules referenced in the barrel file, even if only a few are actually used, leading to slower builds.

Result: In the demo app, removing all barrel files led to a reduction in the number of modules transformed from 42 to 37.

Step 4: Export Functions Directly, Not Objects/Classes

When you export an object or a class containing multiple methods, bundlers often include the entire object or class, including methods that are never actually used.

  • Example: The time.js file in the demo exported a utility object with multiple functions, but only the getTimeInFormat function was actually used. Despite this, the entire utility object ended up in the final bundle.
  • Fix: The solution is to export functions individually. This allows the bundler to tree shake and eliminate any unused utility functions automatically. This change resulted in a slight decrease in the bundle size in the demo.

Step 5: Swap Heavy Libraries for Lighter Alternatives

This step can often yield the most significant reductions in bundle size. Many popular libraries can be quite large and often, smaller, more modern alternatives exist that provide similar functionality.

  • Example: The demo app initially used moment.js, a widely used but relatively large date manipulation library. By checking resources like Bundlephobia, developers can analyze the size of different npm packages. The source suggests dayjs as a smaller and more modern alternative to moment.js. Simply swapping moment for dayjs resulted in an immediate and substantial drop in bundle size.

Pro tip: When choosing libraries, also consider whether they offer ESM (ECMAScript Modules) support, as this significantly improves the effectiveness of tree-shaking.

Step 6: Lazy Load Non Critical Packages

If a particular package or library is not required for the initial rendering or critical functionality of your application, it's often beneficial to load it only when it's needed.

  • Example: The demo app uses Fuse.js for fuzzy search functionality, but this is only required when the user starts typing in the search bar.
  • Solution: Instead of a static import, use dynamic imports to load the package asynchronously when it's needed:

// Lazy load Fuse.js

const Fuse = import("fuse.js").then((module) => module.default);

 

Result: This approach causes fuse.js to be split into a separate chunk, which is only downloaded and executed when the user interacts with the search functionality. This significantly reduces the size of the initial bundle.

Step 7: [React] Lazy-Load Non-Critical Components

The same principle of lazy loading can be applied to React components that are not immediately needed when the application first loads.

  • Example: In the demo, components like Dashboard.jsx and Settings.jsx are only displayed when the user navigates to their respective routes or clicks a specific button.
  • Solution: React provides the React.lazy() function to easily lazy-load components using dynamic imports:
const Settings = lazy(() =>  import("./components/Settings").then((module) => ({ default: module.Settings })));

 

Result: Lazy-loading components in this way leads to a smaller initial bundle, resulting in a faster first load time for your application.

Bonus Tips

Beyond the seven main steps, the source offers a few more ideas to further optimize your bundle size:

  • Add "sideEffects": false in package.json: This explicitly tells Webpack that none of the files in your project have side effects, allowing for more aggressive tree-shaking. However, use this with caution and ensure your codebase truly doesn't have unintentional side effects.

  • Use Bundlewatch or Size Limit to monitor size in CI: Integrate these tools into your continuous integration (CI) pipeline to track your bundle size over time and prevent regressions.

  • Enable more code-splitting or compression with Vite plugins: Explore the available plugins in your build tool (like Vite) to further optimize code splitting and compression algorithms.

Summary

Frontend applications can become bloated over time, impacting performance even without the addition of seemingly "heavy" code. By consistently applying the techniques outlined in this article, you can maintain a fast application, keep build times short and provide a better experience for both your development team and your users. The key takeaways include:

  • Removing side effects from your files.

  • Deleting unused files and packages.

  • Avoiding the use of barrel files.

  • Exporting functions directly instead of objects or classes.

  • Choosing smaller, modern library alternatives.

  • Lazy-loading heavy packages when they are not immediately needed.

  • Lazy loading non critical React components.

By proactively addressing bundle size, you can ensure your React applications remain performant and provide a smooth user experience. The author encourages readers to share any other tips they might have.

 

Ready to transform your business with our technology solutions? Contact Us today to Leverage Our ReactJS Expertise. 

Share

facebook
LinkedIn
Twitter
Mail
React

Related Center Of Excellence