Dynamic imports with React Router

Introduction:

While developing large web applications, performance and ui response time is very important for good user experience. Using various features provided by javascript we can optimize our applications to a great extent depending on our use case for good performance and decreasing response time.

In this blog, I would like to share with you the dynamic import feature that I incorporated with the react router to reduce the bundle size and improve performance.

Dynamic imports:

The normal imports that we use, includes all the files and dependencies that we put in import statements in the final js bundle whether it is used in the code or not, thus increasing the final bundle size.

The main advantage of dynamic import is that it downloads the files and dependencies based on lazy loading. This means they are downloaded and included in final js bundle only when there is a practical use case of that specific file or dependency in the code. This will reduce the final bundle size to a great extent in large web applications and we can achieve memory optimization, reduce the response time over network calls as the bundle size to be downloaded is less and boosts the overall performance.

Normal import with routing:

/Generic imports
import AddAdmin from "./Container/Admin/AddAdmin"
import BookDetails from "./Container/BookDetails/BookDetails"

const App=(props)=>{
  let routes=null;
  routes=(
    localStorage.getItem("loggedIn")!=="yes"?
    <Switch>
       <Route path="/signIn" component={SignIn} />
       <Route path="/" exact component={Login} />
    </Switch>:
   <Switch>
    <Route path="/book" component={Book} />
    <Route path="/" exact component={Book} />
    <Route path="/bookDetails"  component={BookDetails} />
    <Route path="/addAdmin" component={AddAdmin}/>
    <Route path="/logout"component={Logout}/>
    </Switch>

In this code fragment AddAdmin and BookDetails component is only for admins. Hence these two components will never be routed by a normal user in his entire usage as those links and components are never made accessible to him.

But as we have used normal import the final bundle created will include components BookDetails and AddAdmin and their dependencies if any, there by increasing the bundle size. Also, the extra code in the bundle will never be used by the user. This will also lead to extra memory consumption and might also increase the load time as the bundle needs to be downloaded from the network.

Let's look at the bundle size with this type of import:

without dynamic import.JPG

Here we can see that the bundle size is 10.3kb and response time is 998ms.

Dynamic import with routing:

const AddAdmin = React.lazy(()=> import("./Container/Admin/AddAdmin")
const BookDetails = React.lazy(()=> import("./Container/BookDetails/BookDetails"))

const App=(props)=>{
  let routes=null;
  routes=(
    localStorage.getItem("loggedIn")!=="yes"?
    <Switch>
       <Route path="/signIn" component={SignIn} />
       <Route path="/" exact component={Login} />
    </Switch>:
    <Switch>
    <Route path="/book" component={Book} />
    <Route path="/" exact component={Book} />
    <Suspense fallback={<div>Page is Loading...</div>}>
    <Route path="/bookDetails"  component={BookDetails} />
    <Route path="/addAdmin" component={AddAdmin}/>
    <Route path="/logout"component={Logout}/>
    </Suspense>
    </Switch>
  )

We can see that the import style is changed and there is Suspense introduced in routing. Let's look at the significance of these.

The import used here is dynamic import and it uses lazy loading. On initial page loading the components BookDetails and AddAdmin will not be included in the main bundle. Now consider, normal user login into the application . Still the BookDetails and AddAdmin will not be downloaded or included into the bundle as the user does not have access to both the links and components.

Now if the admin logs in and goes to the /bookDetails or /addAdmin links then only the component and related dependencies will be downloaded in different chunks. If even the admin does not visit these links those components will not be downloaded at all. Now as the components and dependencies are downloaded and added dynamically on visit to the link it might take some time and must not put the application in unresponsive state. Hence, Suspense was introduced to add a fallback mechanism till the component becomes active.

Let's look at the bundle size:

with dynamic import.JPG

As we can see, the bundle size is 9.4kb and response time is 960ms. We see that the bundle size is already reduced and response time is about 38ms less. Also, we can see that two additional chunks are downloaded separately for both the components on visiting the link. This has already optimized the system with a very small change in our code.

Here the change observed is less as this is a small POC project. Consider if those components had large computation logic, heavy bootstraps and css or graphics. Then the bundle size might have reduced to a great extent hence improving the speed and performance of the application.

Conclusion:

So we have learned that dynamic import is a great feature introduced and in some use cases might lead to great system optimization. It can also be used with libraries and functions in React, apart from Components.

Do check my LinkedIn page for more interesting posts, articles and updates.