DEV Community

Cover image for Frontend Performance Optimization with Code Splitting using React.Lazy & Suspense đŸ”„
Yuvraj Pandey
Yuvraj Pandey

Posted on

Frontend Performance Optimization with Code Splitting using React.Lazy & Suspense đŸ”„

Frontend performance is important. JavaScript, though written to be a simple language, can produce surprisingly complex code bases making it difficult to scale up. Part of the reason is that there is a wide variety of classes and modules available. Most substantial JavaScript programs and frameworks have many dependencies, which can make a seemingly simple project embed a large amount of code quickly.

The more code a project has, the slower the browser will load. Therefore, you often have to balance the size of your dependencies with the performance you expect out of your JavaScript. Code splitting is a useful way to strike this balance.

What is Code Splitting?

Client Side Rendering (CSR)
Client Side Rendering (CSR)Many JavaScript frameworks bundle all dependencies into one single large file. This makes it easy to add your JavaScript to an HTML web page. The bundle requires only one link tag with fewer calls needed to set up the page since all the JavaScript is in one place. In theory, bundling JavaScript in this manner should speed up page loads and lower the amount of traffic that page needs to handle.
At a certain point, however, a bundle grows to a certain size at which the overhead of interpreting and executing the code slows the page load down instead of speeding it up. This critical point is different for every page, and you should test your pages to figure out where this is. There isn't a general guideline - it all relies on the dependencies which is being loaded.

The key to code splitting is figuring out which parts of a page need to use different JavaScript dependencies. Code splitting allows you to strategically remove certain dependencies from bundles, then insert them only where they are needed. Instead of sending all the JavaScript that makes up the application as soon as the first page is loaded, splitting the JavaScript into multiple chunks improves page performance by a huge margin.

Photo : Crystallize.com

Code splitting is a common practice in large React applications, and the increase in speed it provides can determine whether a user continues using a web application or leaves. Many studies have shown that pages have less than three seconds to make an impression with users, so shaving off even fractions of a second could be significant. Therefore, aiming for three seconds or less of load time is ideal.

Split and Reduce your Bundles

Get rid of anything that takes up too much space. See if there are more lightweight alternatives for the libraries you are using. Using moment.js ? Try out date-fns. Using lodash? Try out lodash-es. Make sure you import only the individual parts that you actually use:

✅ Do 


import find from 'lodash/find'; find([])
Enter fullscreen mode Exit fullscreen mode

❌ Don't 


import _ from 'lodash'; _.find([])
Enter fullscreen mode Exit fullscreen mode

Photo : Crystallize.com

How does code splitting work in React?

Different bundlers work in different ways, but React has multiple methods to customize bundling regardless of the bundler used.

Dynamic imports

Perhaps the simplest way to split code in React is with the dynamic "import" syntax. Some bundlers can parse dynamic import statements natively, while others require some configuration. The dynamic import syntax works for both static site generation and server-side rendering.
Dynamic imports use the then function to import only the code that is needed. Any call to the imported code must be inside that function.

import("./parseText").then(parseText => {
  console.log(parseText.count("This is a text string", "text"));
});
Enter fullscreen mode Exit fullscreen mode

The single bundle used in the application can be split into two separate chunks:
One responsible for the code that makes up our initial route
A secondary chunk that contains our unused code

With the use of dynamic imports, a secondary chunk can be lazy loaded, or loaded on demand. For eg, the code that makes up the chunk can be loaded only when the user presses the button or on execution of certain condition.

Using React.lazy

lazy
React.lazy allows for lazy loading of imports in many contexts. The React.lazy function allows you to dynamically import a dependency and render that dependency as a component in a single line of code. The Lazy component should then be rendered inside Suspense Component which helps to reflect some fallback content meanwhile the lazy component loads.

import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The fallback prop can accept any element of React which will be rendered while waiting for the loading of the Component. The Suspense Component can be placed anywhere above the lazy component. Moreover, multiple lazy components can be wrapped with a single Suspense Component.

import React, { Suspense } from 'react';
const ComponentOne = React.lazy(() => import('./ComponentOne'));
const ComponentTwo = React.lazy(() => import('./ComponentTwo'));
function MyComponent() {
   return (
      <div><Suspense fallback={<div>Loading...</div>}>
         <ComponentOne />
         <ComponentTwo />
      </div>
   );
}
Enter fullscreen mode Exit fullscreen mode

Route based code splitting: It can be difficult to implement code-splitting in code, the bundles can be split evenly, which will improve the experience for the user.

import React from 'react';
import Suspense from 'react';
import lazy from 'react';
import {Route, Switch, BrowserRouter } from 'react-router-dom';
const HomeComponent = lazy(() => import('./routes/HomeComponent'));
const BlogComponent = lazy(() => import('./routes/BlogComponent'));
const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <BrowserRouter> 
      <Switch>
         <Route path={"/home"}>
            <HomeComponent />
         </Route>
         <Route path={"/blog"}>
            <BlogComponent />
         </Route>
         <Route path="/">
            <Redirect to={"/home"} />
         </Route>
      </Switch> 
    </BrowserRouter>
  <Suspense/>
);
Enter fullscreen mode Exit fullscreen mode

Named Exports

React.lazy currently supports only default exports. An intermediate module that re-exports as default has to be created if one wants to import a module that uses named exports. This ensures the working of tree shaking and prevents the pulling in of unused components.

// Components.js
export const Component = /* ... */;
export const UnusedComponent = /* ... */;
// Component.js
export { Component as default } from "./Components.js";
As both React.lazy and Suspense are not available for rendering on the server yet now, it is recommended to use https://github.com/gregberge/loadable-components for code-splitting in a server-rendered app (SSR). React.lazy is helpful for rendering dynamic import as a regular component in client-rendered app (CSR).
Magic Comment at import()
import(
  /* webpackChunkName: "test", webpackPrefetch: true */
  "LoginModal"
)
// or
import(
  /* webpackChunkName: "test" */
  /* webpackPrefetch: true */
  "LoginModal"
)
// spacing optional
"webpackChunkName" : Using this magic comment we can set name for the js chunk that is loaded on demand.
Enter fullscreen mode Exit fullscreen mode

Prefetch in Webpack

import(/* webpackPrefetch: true */ "...")
Enter fullscreen mode Exit fullscreen mode

This "Resource Hint" tells the browser that this is a resource that is probably needed for some navigation in the future.
Browsers usually fetch this resource when they are in idle state. After fetched the resource sits ready in the HTTP cache to fulfill future requests. Multiple prefetch hints queue up and are fetched while idling. When leaving idle state while prefetching to browser may cancel any ongoing fetch (and put the partial response into cache, to be continued with Content-Range headers) and stop processing the prefetch queue.
To sum it up: Fetch while idle.

Preload in Webpack

import(/* webpackPreload: true */ "...")
Enter fullscreen mode Exit fullscreen mode

This "Resource Hint" tells the browser that this is a resource that is definitely needed for this navigation, but will be discovered later. Chrome even prints a warning when the resource isn't used 3 seconds after load.
Browsers usually fetch this resource with medium priority (not layout-blocking).
To sum it up: Fetch like normal, just earlier discovered.


You may see a linting error that says:
Parsing error: 'import' and 'export' may only appear at the top level.
This is due to the fact that the dynamic import syntax is still in the proposal stage and has not been finalized. Although webpack already supports it, the settings for ESLint (a JavaScript linting utility) used by Glitch has not been updated to include this syntax yet, but it still works!


All Good


That's it for this article hope you would have learned something useful from it. So If you have any thoughts or suggestions, feel free to leave a comment below. Don't forget to share your love by clapping for this article as many times you feel like.
You can follow me on Twitter, Github , LinkedIn , Facebook.
Happy Coding đŸ‘šâ€đŸ’» 🎊.

Top comments (2)

Collapse
 
frondor profile image
Federico VĂĄzquez

This can be counter-productive if not used properly.
One can think of this as a solution for every component and make everything lazy-loaded, but then the application performance is going to be negatively impacted by the extra requests and network overhead, as well as the UX with all the Suspense flickering (if not handled properly with something like skeleton placeholders).

I'd say that this approach is good for page contents, and works very well for PWAs applying the App Shell pattern.

Collapse
 
yuvrajpy profile image
Yuvraj Pandey • Edited

Agreed to your thoughts completely
..such approach should be dealt very cautiously as it may lead to weird issues. The only time i would suggest such approach would be when you wana improve your page load time by avoiding loading of components which a user wont require at the start which may include heavy libraries like a PDF renderer etc