How to Fix React Hydration Error in Astro

Ákos KőművesÁkos Kőműves
3 min read

Hi friends 👋

I’ve launched a custom Astro front-end for my Hashnode blog. I wanted to have static pages that work without JavaScript, while still having access to Hashnode’s best-in-class Neptune editor ❤️. Astro generates static pages by rendering your components on the server side. This is a big plus but it introduces the possibility of React Hydration Errors.

A React Hydration Error occurs when the HTML generated by React on the server is different from the HTML generated by React on the client.

If you prefer to watch the video version where I also walk you through how to fix React Hydration Errors in Astro, here’s a video I made for you!

So while static pages worked for 99% of my site, there was a problem…

I wanted a Countdown component that shows you the current promotion I’m running on my React Custom Hooks Course. This component had to be dynamic, counting down the days, hours, minutes, and seconds you have left until the promotion ends.

The new time is set in the useEffect below:

// Countdown.tsx
const Countdown = () => {
  const [time, setTime] = useState(() => formatTime(timeLeft));

  useEffect(() => {
    const interval = setInterval(() => {
      timeLeft = PROMOTION_END - Date.now();
      setTime(formatTime(timeLeft));
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return <span id="countdown">Time left: {time}</span>;
};

To get the time changing I had to make this component client-side rendered with Astro, using the client:load directive:

// header.astro
<Countdown client:load />

Because I made Countdown client-side render, but it was already server-side rendering, I’ve run into the famous React Hydration Error.

When the Countdown component was rendered on the server side, it had one date. However, when it was rendered on the client side a few seconds later, it had a different date.

This is the perfect recipe for a React Hydration Error:

Let’s see two ways to fix this hydration error for our dynamic React component in Astro.

Return Null on Server Side rendering

We know that useEffect doesn’t run while the React component is rendered on the server side. We can use this knowledge to track when the component is client-side rendered by having a boolean state that changes to false when useEffect runs during mount:

const Countdown = () => {
  const [time, setTime] = useState(() => formatTime(timeLeft));
  // true, because we're on the server by default
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setIsLoading(false); // Okay, now we're on the client
    const interval = setInterval(() => {
      timeLeft = PROMOTION_END - Date.now();
      setTime(formatTime(timeLeft));
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  if (isLoading) return null; // while on the server return null

  return <span id="countdown">Time left: {time}</span>;
};

This solution fixes the hydration error, but it makes our component a bit more complicated with an extra state, due to how Astro renders our component.

Not the best solution… Let’s see if Astro can fix the problem!

Use client:only=”react”

Astro gives you Directives that control how Astro hydrates the components it renders.

One of these directives is client:only={string}.

When this directive is applied, Astro skips the server rendering of a component and renders it only on the client. It works like client:load by loading, rendering, and hydrating the component right away when the page loads.

Applying this directive in my case looked like this:

// header.astro
<Countdown client:only="react" />

This also made the hydration error disappear, without changing the complexity of the React component itself!

Win-win!

Conclusion

In conclusion, dealing with React Hydration Errors in Astro can be challenging, but with the right approach, it is manageable. By understanding the root cause of these errors and utilizing Astro's directives effectively, you can ensure your dynamic components work seamlessly.

Whether you choose to return null on server-side rendering or use the client:only="react" directive, both methods offer viable solutions to maintain the integrity of your static and dynamic content.

Happy coding!

0
Subscribe to my newsletter

Read articles from Ákos Kőműves directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ákos Kőműves
Ákos Kőműves

I build web apps and make educational content to help web developers.