DEV Community

Cover image for I Went Down the React Hydration Rabbit Hole
Omaima Ameen
Omaima Ameen

Posted on

I Went Down the React Hydration Rabbit Hole

Okay, I went down the React hydration rabbit hole and, I think the way we usually talk about hydration makes it sound much simpler than what is actually happening.

For manyy many many many manyyy years, whenever performance came up, the conversation was mostly about the network. Compress your HTML. Cache your assets. Split your bundles. Ship less JavaScript, don't make the user download half the internet just to see a landing page. All good adviceπŸ‘ but eventually the JavaScript reaches the browser. AND THEN WHAT?
The browser still has to parse it, compile it, execute it, and React still has to do its own work on the main thread. That part doesn't disappear just because your bundle arrived quickly.

and hydration is not some magical lightweight operation where React looks at the HTML and says, "yep, looks good πŸ‘" and walks away.

there's actual CPU work happening.

The browser already has the DOM because the server sent HTML. React now has to come in and understand that existing DOM, build its Fiber representation, reconcile what it expects with what is already there, attach the necessary behavior, and make that server-rendered UI something React can actually control.

So when I started reading about React 18, the interesting question for me wasn't really "Did React make hydration faster?"

It was more like:

"Wait... what if the problem isn't only how fast we hydrate, but how we schedule the work?"

And THAT is where React 18 gets interesting.

First, React isn't just "attaching event listeners"

This was one of those things that sounded obvious until I actually looked closer.

We casually say:

React hydrates the server-rendered HTML.COOOOL, but internally, React isn't simply wandering through the DOM attaching onClick handlers like some extremely determined intern. πŸ˜‚

The server has already produced HTML. The browser has parsed that HTML and created actual DOM nodes. React then has to reconcile its expected component structure with those nodes and determine whether the existing DOM can be reused.

That's where Fiber becomes important.

During a normal client-side mount, React is creating host nodes as part of rendering the UI. During hydration, those nodes already exist. React has to match its work against something that the browser has already created.

So conceptually, if the server gave us a <div> and React expects a <div> in that position, React can claim and reuse that existing node rather than treating the whole thing like a fresh client render.

And this is why hydration mismatches are more interesting than just "oops, React printed a warning."

React is basically trying to reconcile two realities:

What the server already put in the DOM

AND

What the client-side React tree says should be there.

If those realities don't agree, React has a problem.

You're not just dealing with ugly console output. You're dealing with React failing to establish the relationship it expected between the Fiber tree and the existing DOM.

That little hydration warning suddenly looks a lot less innocent. 😭

then I reached Suspense and things got even weirder because I think a lot of us learn Suspense as basically:

something is loading
|
show fallback

which is technically useful, but it completely hides the interesting part.

Suspense is not just your fancy loading spinner wrapper.

It's a boundary around rendering work.

Take something like:

const Chart = lazy(() => import("./Chart"));

at that moment, React doesn't have the component implementation available immediately. The dependency is still loading and React has a mechanism for dealing with that unfinished work, during rendering, the lazy component can suspend. React then looks for the nearest Suspense boundary and renders the fallback instead of pretending that the component is ready.

The part I find really interesting here is that React hasn't "crashed."

Nothing went wrong in the traditional sense.

The renderer basically encountered work it couldn't finish right now and said:

okay, I can't continue with THIS part. I'll handle the boundary instead.

That's a very different mental model from thinking of Suspense as merely a component that displays "Loading...".

The fallback is the visible part, the boundary is the interesting parta and then there's Selective Hydration.

This is where React 18 starts making a loooooooooooooooooooooot more sense.

Imagine your page has a comments section, and that section is behind a Suspense boundary.

The page is visible. The HTML is already there. But maybe the Comments subtree hasn't finished hydrating yet.

Then the user comes along and immediately clicks something inside it.

Which is basically the most annoying thing a user can do from the perspective of your carefully scheduled rendering work. πŸ˜‚

The user does not care that React is currently busy hydrating something else.

They clicked.

And this is where the React 18 scheduling model matters.

Instead of treating hydration like one giant checklist that must be completed from top to bottom before anything else can happen, React can prioritize work associated with an interaction.

The click is important.

The Suspense boundary containing the target can become a higher-priority hydration target.

React can work on getting that part of the tree ready, and once the necessary React tree is available, the queued event can be replayed.

And I really like this distinction:

The click didn't disappear. React just wasn't ready to process it yet.

That's a very different thing.

The browser received the event.

React's job was figuring out when the corresponding React tree was ready to handle it.

This is one of those details that makes "Concurrent React" sound a lot less mysterious once you stop thinking about it as React doing five things at exactly the same time.

It isn't.

The main thread still exists.

Your CPU did not suddenly grow another brain because you upgraded React. 😭

Which brings us to the browser and this is probablyyyy my second favorite part.

the browser doesn't know React exists, it doesn't care about Fiber it doesn't care about lanes.

It doesn't care that somewhere inside your node_modules there is a Scheduler having an existential crisis.

The browser knows JavaScript.

That's it.

React's scheduler is running inside JavaScript execution.

So when people say React can "interrupt rendering," don't take that too literally.

React can't just freeze arbitrary JavaScript halfway through a function and say, "brb, user clicked."

JavaScript execution is still happening on the main thread.

What React can do is structure its rendering work into units and decide when it should yield control back to the browser instead of continuing to consume the thread.

That distinction matters, React isn't preempting JavaScript.React is cooperating with the browser by yielding.

So the mental model becomes less:

React renders everything simultaneously.

and much more:

React has a bunch of work to do, but not all work deserves to block the user right now.

I think that's the more interesting idea behind Concurrent React, it isn't really about making the CPU magically faster, it is about making React's work interruptible, prioritizable, and schedulable.

That's a much bigger idea than "React 18 made hydration faster."

Because the CPU work still exists.

The JavaScript still exists.

The hydration still exists.

React just got much better at deciding:

"Okay, this can wait. The user just did something." and for a UI library, that's a pretty damn important distinction.

The more I dig into React internals, the more I realize that a lot of the things we use every day as developers are hiding some seriously interesting engineering underneath.

i'm still figuring a lot of this out, so if I've misunderstood something here , please tell me !! I'd genuinely rather be corrected than confidently post something wrong. 😭

would love to know how you think about hydration and Concurrent React, or what React-internals rabbit hole you've fallen into recently.

Drop your thoughts below amazing people πŸ™Œ

Top comments (0)