Two Tips to Improve Performance by 30% With React and Webpack
At Teachers Pay Teachers, we take performance seriously since it is widely accepted that performance delights users, increases search engine rankings and improves conversion rate. In this post, we’ll talk about the front-end optimizations we’ve made to our recently updated product page to boost our webpagetest Speedindex performance by 30%!
We use React and Webpack to bundle and execute our JavaScript. We also utilize server side rendering to ensure the payload we deliver to the client is already populated with data. We also took advantage of ?react_perf
to easily identify via flame graphs which components were performance bottlenecks. (Note: appending ?react_perf
to your URLs only works in Chrome when NODE_ENV is not set to production.) Now that you understand the technologies we're using, let's dive into the two tips you can use to optimize your pages!
Asynchronously Loaded Modules #
We use webpack’s code splitting functionality to create a separate JS bundle per route of our site. This allows us to ensure we load only the JS that’s required for displaying a given page. We wanted to take this one step further and initially load the bare minimum that’s required for displaying immediately visible content on the page and defer loading everything else until later.
Additionally, we found an awesome library called react-async-component that allows us to split certain parts of the page into their own, separate, JavaScript bundles. Here’s an example:
Which makes it super simple to replace existing components. Just swap this:
import DialogModalPrefab from "DialogModalPrefab";
<DialogModalPrefab />;
with this:
import DialogModalPrefabAsync from "DialogModalPrefabAsync";
<DialogModalPrefabAsync />;
If you’re wondering what that /_ webpackChunkName _/
comment is about, check out the docs for webpack’s import and output .
Decrease Bundle Size #
By loading the bare minimum that’s required for displaying immediately visible content on the page and loading everything else asynchronously, we were able to shrink our page specific bundle by 177 KB (44 KB gzipped). This decrease in initial bundle size means DOMContentLoaded happens sooner. This is because the entire page is blocked while parsing and executing its required JavaScript - including components that are below the fold or not visible until interaction! We took our page bundle from this:
To this:
Lower Time to first Byte #
Loading bundles asynchronously also reduces the amount of time spent server side rendering (we use serverMode: ‘defer’
in react-async-component) as there is a shallower component hierarchy to parse and render. This leads to a reduction in time to first byte which ultimately allows our users to interact with our pages more quickly.
Each one of the vertical lines is a deployment of another asynchronous bundle.
Should Component Update #
React provides a component lifecycle hook called shouldComponentUpdate
that allows us to short-circuit unnecessary renders. We have a number of high level components that are responsible for fetching data. Whenever new data is fetched, it triggers a render of all of its children. Rendering all children due to a single piece of data updating can be costly and unnecessary when the fetched data doesn’t modify the layout. Fortunately, with a couple of strategically placed shouldComponentUpdate
s we were able to reduce the amount of time spent in scripting, layout, and painting considerably. Here is a zoomed in view of a 32ms improvement due to shouldComponentUpdate
.
Before:
After:
Given JavaScript runs on a single thread in an event loop, yielding control flow back to the browser as soon as possible makes the page feel snappier as it’s able to immediately respond to user input.
Every little win counts #
Over the past 90 days, Speedcurve has recorded a 30% improvement in our speed index.
While we’ve made some backend optimizations in our Elixir API, these front-end optimizations have been much more substantial! Remember, when it comes to performance, every little win counts!
I want to give a special shout out to Peleg, Stephen, and Tim of TpT's web platform team for building the infrastructure that made all of this possible!
Do you want to know more about how we implemented shouldComponentUpdate
? Curious about how we track bundle sizes over time? Have a story of your own to share? Talk to us in the comments section!