https://www.joshwcomeau.com/react/the-perils-of-rehydration/ JoshWComeau * Latest * Posts * Snippets * Goodies HomeTutorialsReact The Perils of Rehydration An Eye-Opening Realization about React Table of Contents IntroductionSome problematic codeServer-side rendering 101Code on the clientDynamic sectionsSchrodinger's userA noble but flawed attempt Rehydration [?] renderThe solutionTwo-pass renderingPerformance implicationsAbstractionsMental models Introduction I ran into the strangest issue recently. Everything was groovy in development, but in production, the bottom of my blog was doing something... unintended: A rendering issue causes a newsletter signup form to be squashed and overlapping the article / footer contentA hot mess of UI soup A bit of digging into the Elements tab in the devtools revealed the culprit... My React component was rendering in the wrong spot! html html How could this be? Had I discovered a bug in React? I checked the React Devtools "[?][?] Components" tab, and it told a different story, one in which everything was fine, and the pieces were all where they were supposed to be. What a liar! It turns out, I had a fundamental misunderstanding about how React works in a server-side-rendering context. And I think many React devs share this misunderstanding! And it can have some pretty serious ramifications. Link to this heading Some problematic code Here's an example of code that can cause the kind of rendering issue shown above. Can you spot the problem? jsx For a long time, I would have believed that this code was A-OK. Right up until my blog started impersonating a Picasso painting. This tutorial will peek behind the curtain to help us understand how server-side rendering works. We'll see why the logic shown here can be problematic, and how a different approach can accomplish the same goal. Link to this heading Server-side rendering 101 To understand the problem, we need to first dig a little into how frameworks like Gatsby and Next.js differ from traditional client-side apps built with React. When you use React with something like create-react-app, all of the rendering happens in the browser. It doesn't matter how large your application is, the browser still receives an initial HTML document that looks something like this: html The page is fundamentally empty, but it includes a couple JS scripts. Once the browser downloads and parses those scripts, React will build up a picture of what the page should look like, and inject a bunch of DOM nodes to make it so. This is known as client-side rendering, since all the rendering happens on the client (the user's browser). All of that stuff takes time, and while the browser and React are working their magic, the user is staring at a blank white screen. Not the best experience. Smart people realized that if we could do that rendering on the server, we could send the user a fully-formed HTML document. That way, they'd have something to look at while the browser downloads, parses, and executes the JS. This is known as server-side rendering (SSR). Server-side rendering can be a performance win, but the thing is, that work still needs to be done on-demand. When you request your-website.com, React has to transform your React components into HTML, and you'll still be staring at a blank screen while you wait for it. It's just that the work is being done on the server, not on the user's computer. The galaxy-brain realization is that huge chunks of many websites and apps are static, and they can be built at compile-time. We can generate the initial HTML way ahead of time, on our development machines, and distribute it immediately when a user requests it. Our React apps can load as quickly as a vanilla HTML site! This is exactly what Gatsby does (along with Next.js, in certain configurations). When you run yarn build, it generates 1 HTML document for every route on your site. Every side page, every blog post, every store item -- an HTML file is created for each of them, ready to be served up immediately. Is this all just server-side rendering? Unfortunately, a lot of this language is used interchangeably, and it can be kinda hard to follow. Technically, what Gatsby does is server-side rendering, since it renders the React app using Node.js using the same ReactDOMServer APIs as a more traditional server-side render. In my mind, though, it's conceptually different; "server-side rendering" happens on your live production server in real-time, in response to a request, whereas this compile-time render happens much earlier, as part of the build process. Some folks have started calling it SSG, which either stands for "Static Site Generation" or "Server-Side Generated", depending on who you ask. Link to this heading Code on the client The apps we build nowadays are interactive and dynamic--users are accustomed to experiences that can't be accomplished with HTML and CSS alone! So we still need to run client-side JS. The client-side JS includes the same React code used to generate it at compile-time. It runs on the user's device, and builds up a picture of what the world should look like. It then compares it to the HTML built into the document. This is a process known as rehydration. Critically, rehydration is not the same thing as a render. In a typical render, when props or state change, React is prepared to reconcile any differences and update the DOM. In a rehydration, React assumes that the DOM won't change. It's just trying to adopt the existing DOM. Link to this heading Dynamic sections This takes us back to our code snippet. As a reminder: jsx This component is designed to have three possible outcomes: * If the user is logged in, render the component * If the user is NOT logged in, render the component. * If we don't know if the user is logged in or not, render nothing. Link to this heading Schrodinger's user In a macabre thought experiment, Austrian physicist Erwin Schrodinger describes a situation: a cat is placed in a box with a toxin that has a 50% chance of being released within an hour. After an hour, there is an equal probability that the cat is alive or dead. But until you open the box and find out, the cat can be thought of as both alive and dead*. In our webapp, we face a similar predicament; for the first few moments that a user is on our site, we don't know whether they are logged in or not. This is because the HTML file is built at compile-time. Every single user gets an identical copy of that HTML, regardless of whether they're logged in or not. Once the JS bundle is parsed and executed, we can update the UI to reflect the user's state, but there is a significant gap of time before that happens. Remember, the whole point of SSG is to give the user something to look at while we download, parse, and rehydrate the app, which can be a lengthy process on slow networks/devices. Many webapps choose to show the "logged out" state by default, and this leads to a flicker you've probably run into before: The Guardian news website shows a 'Sign in' link, before replacing it with 'Your account'. Airbnb makes the same mistake, defaulting to a logged-out navigation bar. I took the liberty of building a mini Gatsby app that reproduces this issue: On 3G speeds, the wrong state is shown for quite a while! If you'd like, you can give this a whirl yourself. Click the "Log in" link to fake-login, and click again to log out. Link to this heading A noble but flawed attempt In the shared code snippet, we attempt to solve for this problem in the first few lines: jsx The idea here is sound: Our initial compile-time build happens in Node.js, a server runtime. We can detect whether or not we're rendering on the server by checking to see if window exists. If it doesn't, we can abort the render early. The problem is that in doing so, we're breaking the rules. Link to this heading Rehydration [?] render When a React app rehydrates, it assumes that the DOM structure will match. When the React app runs on the client for the first time, it builds up a mental picture of what the DOM should look like, by mounting all of your components. Then it squints at the DOM nodes already on the page, and tries to fit the two together. It's not playing the "spot-the-differences" game it does during a typical update, it's just trying to snap the two together, so that future updates will be handled correctly. By rendering something different depending on whether we're within the server-side render or not, we're hacking the system. We're rendering one thing on the server, but then telling React to expect something else on the client: html html Somewhat remarkably, React can still handle this situation sometimes. You may have done this yourself, and gotten away with it. But you're playing with fire. The rehydration process is optimized to be [?][?] fast [?][?], not to catch and fix mismatches. Link to this heading About Gatsby in particular The React team knows that rehydration mismatches can lead to funky issues, and they've made sure to highlight mismatches with a console message: A dev-tools console error message: "Warning: Expected server HTML to contain a matching
in