Repository: react
Issue: Inability to prioritise hydration & react yields too willingly 
Labels: Type: Discussion

# Summary 

React does not provide an API to set the priority on a component or suspense boundary for hydration.  This means you cannot optimise your application to hydrate part of the app you know users will want to interact with first.  Or in my case a part of the application I need to start rendering client side asap to replace a SSR skeleton with the correct personalised content that can only be rendered client side. 

The issue is compounded by the way React 18 yields the main thread during hydration to other higher priority events, which while a great idea in theory to improve FID and INP, in practice means 3rd party loaded (gtm) scripts delay the initial hydrating.  It would be great if this logic could differentiate between user interactions and scripts added to the call stack? 

Detailed here https://github.com/reactwg/react-18/discussions/38#discussioncomment-837161 

My real-world use case is an ecommerce application where marketing teams are loading via gtm vast amounts of 3rd party scripts, think tiktok, instagram, bing, and masses of gtm containers.  While the ideal solution would be to either trim these down or move them to the worker threads via something like partytown (https://partytown.builder.io/), neither is realistic.  As such we need a way to optimise around them. 

We can see, on slower windows machines and older mobiles, initial hydration of components take more than 5-8 seconds to start. This provides a negative experience to customers where skeletons are visible, and a worse UX than the legacy MVC sites. 

##Work arounds 

As detailed here there are potential workarounds that already exist 

https://github.com/reactwg/react-18/discussions/130 

Firstly the `unstable_scheduleHydration` API, however this has since been moved to the hydrateRoot function and is no longer accessible in Next.js (I believe?) (https://github.com/facebook/react/pull/22455/files)  

The second, and this is where you have to forgive me, is to creatively interpret the following 

` Discrete events (eg. click/keypresses) trigger synchronous (selective) hydration in the capture phase if the code in its encapsulating Suspense boundary is ready. 

If the event can't be synchronously hydrated then we'll increase the priority of that boundary so it hydrates first when it's ready.` 

Which leads to this being an incredible performance optimisation 

``` 

  useEffect(() => { 

      document.getElementById("body")?.click(); 

  }, []); 

``` 

Because React batches hydration together at Suspense layers, it does an initial render down to the Suspense boundary, then processes the useLayoutEffects, then useEffects, then finally starts the child boundary. This logic when applied in the parent and clicking an element in the first child boundary, forces React into a synchronous render, no longer yielding, and prioritising above all other suspense boundaries the one you clicked. 

 

## Evidence 

Reproducible example here https://github.com/edqwerty1/hydrate 

Visit http://localhost:3000/slow for no click logic, and http://localhost:3000/fast for the improved version. 

In this scenario I really need Content Right to hydrate first as I must replace a SSR skeleton with my personalised content 

### Before  

<img width="345" alt="image" src="https://github.com/user-attachments/assets/1410a856-e8c8-4bbc-974c-0d573666e0c2">

It is delayed by around 5 seconds, these timings are realistic to real world data.  The gtm scripts don’t normally block for an entire second, but there are normally a lot more of them. 
 
### After 

<img width="342" alt="image" src="https://github.com/user-attachments/assets/446cfd41-2977-4fe9-bc7e-4c08ca0959b0">

Content Right hydrated in 2.5s, half the time. And more importantly the time between App starting and my component rendering has reduced from 2.3s to 200ms! A very large saving and if I reorder the initial script tags I could in theory delay the 3rd party code until my entire app is ready.   

Now this of course isn't idea, by hacking the hydration logic I am potentially causing higher INP scores for real events, and there may be other issues.