July 1, 2024

React Hydration Mismatch Error

I've been using React/Next.js for a long time and should know this, but I went down a rabbit hole this weekend trying to figure out why a className wasn't able to be reconciled on the client during the initial rendering.

I've gotten the dreaded hydration mismatch many times in the past and it was always due to a bug, but in this instance, I didn't see it that way. I wanted a nested component's className to be "mobile-first" optimistic. The component should only be shown on small viewport. Hence, pre-render on the server as if it was in a small viewport. Once it renders on the client, update to the className based on the actual conditions.

React refused to make this reconciliation and I couldn't understand why. All of my searching only gave me vague and generic guidance, which was "this should be treated as a bug and fixed". I finally found the reasoning in React's hydrateRoot docs:

In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive.

So basically, if your component has client-dependent conditions (like window.matchMedia) that affect its initial attribute state, it should return two separate trees: one for the server and one for the client.