Website Performance with React and Next.js
With the surplus of front end frameworks to choose from, it would be an oversight to assume we're automatically covered in terms of performance. Among the many recipes of tech stacks, a popular combination these days is React with Next.js.
React provides a declarative, component driven framework for rendering UIs while Next.js concerns itself with the mechanics of how React code is implemented and a means of creating a full-stack React website. The primary reason for using Next.js is the ease of building a "universal" React app rendered both client side and server side (Server Side Rendering or SSR).
What is React Reconciliation and Next.js Hydration?
One interesting characteristic of React is its implementation of a "virtual DOM" - an entirely separate instance of a DOM tree kept in memory and synced with the "real" DOM by a process called reconciliation. React's diffing algorithm, as explained, is quite complex and can introduce many pitfalls for engineers, especially if misunderstood. If engineers are not careful when handling data flow to components, React can have slow performance from UI repaints. This situation can be especially damaging when adding server side rendering to the equation. With server side rendering, we now have additional complexity of maintaining parity between server and client side renders, risking performance. This risk is especially critical considering Google's impending search algorithm change to prioritize page experience. We can actually screw up a website's SEO when not supporting React's reconciliation and effectively Next.js hydration 😨.
Visualizing React Reconciliation and Next.js Hydration Performance with Chrome's DevTools
To understand the impact of client side and server side rendering parity, let's pick on Ticketmaster 👺. I found our guinea pig by peeking through example websites on the Next.js showcase. Many other websites from the showcase suffer from symptoms I'm about to illustrate.
The performance panel in Chrome DevTools was recently overhauled and now provides even more useful information. As you can see below, it shows a timeline of important metrics including Google's core web vitals First Contentful Paint (FCP) and Largest Contentful Paint (LCP). It also shows key timings like "Next.js before hydration" and "Next.js hydration". Note the tiny screenshots, timings and how they relate to each other.
Notice anything unusual? It might not be obvious, considering the thumbnail screenshot size, but with a closer look it seems that images appeared, disappeared and then re-appeared 😕.
This effect is a bi-product of a mismatch in client side and server side rendering. Thanks to Next.js, we had everything we needed from server side render, but during React reconciliation and effectively Next.js hydration the image elements were repainted. In React terms this isn't a "re-render" but actually a teardown of one DOM tree and a replacement of a new one (not necessarily the whole DOM tree).
Missing parity between client side and server side rendering can produce repaints that impact performance as defined by Google's core web vitals. This type of issue is important to consider since it occurs on page load which would effectively impact SEO.
Using Foo to Monitor Website Performance
In this post we talked about how Google will prioritize web page experience in regards to SEO. Google recommends utilizing tools like Lighthouse (lab data) to measure website performance and Web Vitals (field data) for direct insight. Foo offers a solution to monitor websites by automatically running Lighthouse. It also provides a service to collect and analyze Web Vitals.
It's easy to get caught up in the romance of new frameworks but it's important to understand all implications they hold. I hope this post shed light on the complex topics of React reconciliation, Next.js hydration and server side rendering. I also hope you can find Foo's Lighthouse and Web Vitals services to be effective in promoting this knowledge.