This post is about how we improved our website’s time to first render by optimizing the size of critical bundles and using resource hints to achieve an optimal script loading order.

Background

In our previous post about performance optimization on Grofers.com, we discussed how we split our single large JS bundle into two smaller bundles. One of the two bundles contained all the third-party libraries, which could be effectively cached because it did not change often. The other was a smaller application bundle which would get downloaded whenever we made a change to the application code. It helped us improve our page load time, but as our application grew, our application bundle grew in size too.

Every time a user would open a page, a large chunk of non-critical code was loaded to render the page. For instance, just for rendering the homepage we didn’t have to load the scripts responsible for our product listing page or checkout page. This was a problem for us, and it could be solved only by building much smaller route specific bundles.

Code Splitting

As mentioned in the previous section, we were still serving users an application bundle which contained all our application code regardless of which page the user was browsing. Our application bundle size reached about 226KB. Splitting the code by the routes was our best bet.

Waterfall diagram highlighting initial vendor and application bundles

We use React Router 3 which provides getComponent, that allows us to load a component only when a particular route is mounted. Webpack’s require.ensure can be used to define which components have to be loaded for a particular route.

An example of what a route looks like in our routes definition

With such a configuration of routes, Webpack would build a small bundle for each route which could be loaded as and when required (when appropriate route is activated), that improved the scenario vastly. The bundles which were critical for rendering the homepage came down to about 110KB, as compared to 226KB, which was being loaded every time when had we not split the application bundle by the routes.

Waterfall diagram highlighting smaller route specific bundles

Resource Hints

At first, lazy loading of route specific bundles introduced some visible delay while navigating to a different route. This was because whenever the route changed, the required bundle would be asynchronously fetched before it could be used for rendering the page.

A solution to this problem, we thought, would be to keep the required bundles ready before the user navigates to a different route. We wanted to fetch all bundles when the user loads the website while making sure that the critical bundles are loaded first. We were approaching towards adopting the PRPL pattern for improving web app performance, relying on browser cache for caching the prefetched bundles.

This is where using resource hints helped us. We used preload with the critical bundles to let the browser know that these bundles should be fetched on high priority and the rest of the bundles should be fetched without blocking the page, with the help of defer. Using the output provided by the Webpack’s StatsPlugin, we wrote some code that would generate, for a given route, lists of bundles to be fetched with preload and defer.

This worked well and any perceived delay due to lazy loading was eliminated.

What’s next?

We made these tweaks a few months ago when we were using Webpack 1.12. There are new constructs for code splitting available in Webpack 2 onwards, which we are yet to try out. Moreover, there are said to be built-in optimizations in the newer versions which result in smaller bundle sizes. Hopefully, replicating these changes with the newer version of Webpack will help us shrink our bundles even further.

Naturally, if you enjoyed reading this and thought this was an interesting challenge, we are always looking for talented, curious people to join the team. Also, follow us on Twitter to know what’s going on at Grofers Engineering.