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%!
?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.
Which makes it super simple to replace existing components. Just swap this:
import DialogModalPrefab from 'DialogModalPrefab'; <DialogModalPrefab />
import DialogModalPrefabAsync from 'DialogModalPrefabAsync'; <DialogModalPrefabAsync />
Decrease Bundle Size
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
shouldComponentUpdates 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
Every little win counts
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!
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!