Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inability to prioritise hydration & react yields too willingly #31099

Open
edqwerty1 opened this issue Sep 30, 2024 · 2 comments
Open

Inability to prioritise hydration & react yields too willingly #31099

edqwerty1 opened this issue Sep 30, 2024 · 2 comments

Comments

@edqwerty1
Copy link

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 reactwg/react-18#38 (comment)

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

reactwg/react-18#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

image

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

image

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.

@edqwerty1 edqwerty1 added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Sep 30, 2024
@devknoll
Copy link
Contributor

devknoll commented Oct 1, 2024

I think you can get the same effect as your click hack by using:

flushSync(() => {
  root.unstable_scheduleHydration(document.getElementById("body"));
});

This doesn't help you in Next.js, since it doesn't expose a way to use unstable_scheduleHydration... but it probably should?

@eps1lon
Copy link
Collaborator

eps1lon commented Oct 8, 2024

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.

React will do it for you. If a user clicks into a boundary, React will prioritize hydration of the nearest Suspense boundary.

@eps1lon eps1lon added Type: Discussion and removed Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug labels Oct 8, 2024
@eps1lon eps1lon changed the title Bug: Inability to prioritise hydration & react yields too willingly Inability to prioritise hydration & react yields too willingly Oct 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants