I'm always excited to take on new projects and collaborate with innovative minds.

Address

Kayseri, Turkey

Social

Hydration and useSyncExternalStore in React: Problems and Solutions

In this post, I'll explain how we can prevent hydration mismatches in React with the useSyncExternalStore hook, and how combining it with Suspense and useDeferredValue makes the whole process smoother.

Hydration and useSyncExternalStore in React: Problems and Solutions

🔍 What Is Hydration?

Hydration is the process in which React matches the HTML output generated on the server with interactive components in the browser.

In other words, when the page first loads, the user actually sees static HTML. Then React “brings it to life” and makes things like buttons and inputs interactive.

Sometimes, however, the HTML rendered on the server and the React tree built on the client don't match exactly. When that happens, React throws a “hydration mismatch” error or is forced to re-render the component.


💡 What Is useSyncExternalStore?

useSyncExternalStore is a hook introduced in React 18.
It lets you work with external data sources (e.g. a global store, an event emitter, etc.) in a way that's compatible with SSR + hydration.

Its basic usage looks like this:

const isSSR = React.useSyncExternalStore(
  () => () => {},        // subscribe: değişiklikleri dinler
  () => false,           // getSnapshot: istemci tarafında çağrılır
  () => true             // getServerSnapshot: sunucu + hydration sırasında çağrılır
)

 

With this setup, React keeps the output consistent between the server and the client.
That is, during hydration it returns exactly the value that's expected, so no mismatch occurs.


⚠️ Pitfalls Between useSyncExternalStore and Suspense

While useSyncExternalStore may look like a great solution, some problems show up, especially when it's used together with the Suspense component:

  1. Flickering Suspense fallbacks
    The page first shows the server output, then a loading spinner appears, and then the real content comes back. From the user's point of view, this “flicker” isn't a pleasant experience.
  2. Updates reaching components that haven't hydrated yet
    If the store changes before some components become interactive, hydration errors can occur. In that case, React is forced to re-render.
  3. Poor INP (Interaction to Next Paint) scores
    Because useSyncExternalStore triggers synchronous rendering, it blocks the main thread. This causes delays in interactions.
  4. Loss of control over render timing
    The hook disables React's “time slicing” advantage, which can hurt performance.

🧩 The Solution: Using It Together with useDeferredValue

This is where the useDeferredValue hook comes in to solve these problems.
By handling the value returned from useSyncExternalStore in a deferred way, we can prevent both hydration mismatches and performance issues.

Here's an example implementation:

 

const emptySubscribe = () => () => {}
const returnFalse = () => false
const trueOnServerOrHydration = () => true

function useIsSSR() {
  const isSSRSync = React.useSyncExternalStore(
    emptySubscribe,
    returnFalse,
    trueOnServerOrHydration
  )
  return React.useDeferredValue(isSSRSync)
}

function App() {
  const isSSR = useIsSSR()
  return isSSR ? "Server output" : "Client output"
}

🔍 How Does It Work?

  • On the first render (the SSR + hydration phase), isSSR is true.
    This way, React uses the output from the server directly → no mismatch.
  • React then produces isSSRSync = false on the client side.
  • But useDeferredValue doesn't reflect this change right away → the user doesn't see a “flickering fallback”.
  • After that, React switches to the client-side content in a non-blocking (concurrent) way.

As a result:

  • Hydration errors are prevented ✅
  • Suspense runs more smoothly ✅
  • INP is preserved and the user experience gets smoother ✅

🧠 Bonus: Improving Stability with Memoization

On top of that, the article recommends keeping component references stable with tools like useMemoOne.
This improves performance by preventing unnecessary re-renders across Suspense boundaries.


🚀 Conclusion

In React, SSR and hydration are critical for giving users a fast and seamless initial load experience.
useSyncExternalStore is a powerful tool for keeping this process under control — but if it isn't used correctly, it can lead to performance problems.

The best approach is to use this hook together with useDeferredValue.
That way, you can get rid of hydration errors and deliver a smoother user experience.

Author Ahmet Yüceler

I build web and mobile products with 6+ years of experience. I share what I learn here.

Get in touch →

Leave a comment

Your email address will not be published. Required fields are marked *