Home >Web Front-end >JS Tutorial >Quick Optimization for your ReactJS Application for Performance and Size

Quick Optimization for your ReactJS Application for Performance and Size

Linda Hamilton
Linda HamiltonOriginal
2024-12-10 20:13:10632browse

React being massively used for frontend-intensive applications comes with its unique ways of performance and size optimizations. Improving both will have a considerable measurable impact on the React package bundle size. The lower the bundle size, the faster the loading time, considering that we are focusing on client-rendered applications.

Server-side rendering would further improve load time. In server-side rendering when a user requests a web page, the React components are rendered as HTML code in the server itself. Then this pre-rendered page is sent to the browser, allowing the user to see the page immediately without the overhead of the JS loading time.

But that’s a different story altogether. Let’s mainly focus on trying to improve our client-side rendered site by working on improving the Package bundle size by making tweaks in the code. Let’s dive deep.

1. Code Splitting and Dynamic Imports

“Bundling” of React code is the process of following through all imports and codes and combining it into a single file called a ‘Bundle’. Webpack, Browserify, etc., already do this for us.

Webpack has a feature called ‘Code Splitting’ that is responsible for splitting a single bundle into smaller chunks, deduplicating the chunks, and importing them ‘on demand’. This significantly impacts the load time of the application.

module.exports = {
  // Other webpack configuration options...
  optimization: {
    splitChunks: {
      chunks: 'all', // Options: 'initial', 'async', 'all'
      minSize: 10000, // Minimum size, in bytes, for a chunk to be generated
      maxSize: 0, // Maximum size, in bytes, for a chunk to be generated
      minChunks: 1, // Minimum number of chunks that must share a module before splitting
      maxAsyncRequests: 30, // Maximum number of parallel requests when on-demand loading
      maxInitialRequests: 30, // Maximum number of parallel requests at an entry point
      automaticNameDelimiter: '~', // Delimiter for generated names
      cacheGroups: {
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

Lazy Loading Components with React Suspense (React 18): This combined with dynamic imports will show a visible improvement in Component Loading time.

Generally, when we import child components within a parent component, we import it statically. To prevent importing this component till we actually have to render it, we can use a combination of dynamic imports with React Suspense. React Suspense enables loading a component on demand. It shows a Fallback UI while the corresponding components are dynamically imported and then rendered.

import { lazy } from 'react';

// The lazy loaded Component has to be exported as default
const BlogSection = lazy(() => import('./BlogSection.tsx'));

export default function HomePage() {
  return (
    <>
      <Suspense fallback={<Loading />}>
        <BlogSection />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>Component is Loading...</h2>;
}

2. Tree Shaking

This is a technique used by JavaScript bundlers to remove all unused code before creating bundles. ES6 code can be tree-shaken; however, code that is based out of CommonJS (i.e., uses ‘require’) cannot be tree-shaken.

Webpack Bundle Analyzer is a plugin that will help you visualize the size of a webpack with an interactive map.

npm install --save-dev webpack-bundle-analyzer
npm install -g source-map-explorer

Then configure your webpack to add the above as a plugin:

plugins: [
  new BundleAnalyzerPlugin(),
  new HtmlWebpackPlugin({
    template: './public/index.html', // Path to your HTML template
    filename: 'index.html', // Output HTML file name
    inject: true, // Inject all assets into the body
  }),
];

Make sure your script is configured to run Webpack:

"build": "webpack --config webpack.config.js --mode production"

Run yarn build to generate a report.html that will help you visualize your bundle size effectively.

It will look something like this:

Quick Optimization for your ReactJS Application for Performance and Size

3. Concurrent Rendering

Let’s start by understanding what Blocking Rendering is. Blocking rendering is when the main thread (UX updates) is blocked because React was doing some less important tasks in the background. This used to be the case till React 16.

React 18 has introduced concurrent features, which means it will:

  • Give you more control around how background updates get scheduled and will create a smooth end-user experience by not blocking the main thread.
  • Initiate automatic batching of state updates: Batching refers to grouping multiple re-renders due to multiple state updates in a way that the state updates just once.

Use the startTransition() hook to manage React updates as non-urgent, helping React prioritize urgent updates like user-input and user-interaction with components over the prior.

module.exports = {
  // Other webpack configuration options...
  optimization: {
    splitChunks: {
      chunks: 'all', // Options: 'initial', 'async', 'all'
      minSize: 10000, // Minimum size, in bytes, for a chunk to be generated
      maxSize: 0, // Maximum size, in bytes, for a chunk to be generated
      minChunks: 1, // Minimum number of chunks that must share a module before splitting
      maxAsyncRequests: 30, // Maximum number of parallel requests when on-demand loading
      maxInitialRequests: 30, // Maximum number of parallel requests at an entry point
      automaticNameDelimiter: '~', // Delimiter for generated names
      cacheGroups: {
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

In this example, when the input value changes, the handleChange function is called. The startTransition function is used to mark the update to the list state as non-urgent. This allows React to prioritize the update to the value state, ensuring that the input remains responsive even when the list is large.

Use the useDeferredValue hook to defer a value (usually an expensive calculation) until the UI is less busy.

import { lazy } from 'react';

// The lazy loaded Component has to be exported as default
const BlogSection = lazy(() => import('./BlogSection.tsx'));

export default function HomePage() {
  return (
    <>
      <Suspense fallback={<Loading />}>
        <BlogSection />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>Component is Loading...</h2>;
}

In this example, the useDeferredValue hook is used to defer the value state until the UI is less busy. This helps keep the input responsive by deferring the rendering of the large list until after the input update is processed.

Key Benefits of Concurrent Rendering:

  • Improved Responsiveness: By allowing React to interrupt rendering work, the UI remains responsive to user interactions.
  • Prioritization: React can prioritize urgent updates over non-urgent ones, ensuring a smoother user experience.
  • Better Performance: Expensive updates can be deferred, reducing the impact on the main thread and improving the app’s overall performance.

4. Support Pre-loading of Resources (React 19)

If you are aware of any heavy resources that your application would be fetching during loading, then a good idea would be to preload the resource. These resources could be fonts, images, stylesheets, etc.

Scenarios where preloading would be beneficial:

  • A child component would use a resource. In that case, you can preload it during the rendering stage of the parent component.
  • Preload it within an event handler, which redirects to a page/component that would be using this resource. This is, in fact, a better option than preloading it during rendering.
module.exports = {
  // Other webpack configuration options...
  optimization: {
    splitChunks: {
      chunks: 'all', // Options: 'initial', 'async', 'all'
      minSize: 10000, // Minimum size, in bytes, for a chunk to be generated
      maxSize: 0, // Maximum size, in bytes, for a chunk to be generated
      minChunks: 1, // Minimum number of chunks that must share a module before splitting
      maxAsyncRequests: 30, // Maximum number of parallel requests when on-demand loading
      maxInitialRequests: 30, // Maximum number of parallel requests at an entry point
      automaticNameDelimiter: '~', // Delimiter for generated names
      cacheGroups: {
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

Interesting fact: After implementing preloading, many sites, including Shopify, Financial Times, and Treebo, saw 1-second improvements in user-centric metrics such as Time to Interactive and User Perceived Latency.


Quick Optimization for your ReactJS Application for Performance and Size

Please leave a feedback <3

I hope you found this blog helpful! Your feedback is invaluable to me , so please leave your thoughts and suggestions in the comments below.

Feel free to connect with me on LinkedIn for more insights and updates. Let's stay connected and continue to learn and grow together!

The above is the detailed content of Quick Optimization for your ReactJS Application for Performance and Size. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn