Get CV

Debugging

Hydration that breaks on the locale, not on the markup

The server rendered Russian from the URL. The first client pass read navigator.language. Vue threw the tree. The page still worked, so the bug hid until the header jumped.

On /ru/notes the language switcher flashed German for one frame. Vue had thrown the header subtree. A hydration warning means the server HTML and the first client render disagree. Vue drops that subtree and builds it again. The notes page still answered clicks, so I ignored the warning until the chrome jumped.

The server entry reads the locale from the path. The client entry, on first paint, still had a fallback to navigator.language. Chrome on this machine is de-DE. Half the chrome strings swapped. Dates swapped. The article body came from the API as ru, so only the shell mismatched. That is enough. Users on a Russian URL saw a German header until hydrate finished.

The mismatch was not a missing div. It was two sources of locale on one request.

Two trees disagree. Vue throws the server markup for that subtree.
Two trees disagree. Vue throws the server markup for that subtree.

One locale string

The URL prefix is the source. nginx already maps unknown prefixes away. Node and Laravel both get en, ru, or de. I serialize that value into the page state and the client reads it from there before the first render. navigator.language is allowed after mount, for a suggestion, not for the tree.

The same rule applies to time zone and to the theme. A stored theme in localStorage is unknown on the server. Render the default on both sides, then switch after mount. A flash is better than a thrown tree that puts a click handler on the wrong node.

I had also passed Accept-Language into the i18n plugin on the client while the server ignored that header. Two bootstraps, two answers. The API locale from the URL won for the body. The plugin won for the shell. Pinning one string in the payload closed both.

Pin the locale at the edge. Map en-US to en before Node and PHP see it.
Pin the locale at the edge. Map en-US to en before Node and PHP see it.

How I catch it

Hydration warnings are errors in the Vue process during development. I run one notes detail, one notes list, one admin form. I read only the first mismatch. The rest are usually children of that node.

Invalid HTML still causes the same class of bug. A div inside a p in a seeded body gets repaired by the browser parser. The client tree can never match the server string. The fix is in the article HTML, not in i18n. I saw that on one long note after the locale was already pinned. The warning looked the same. The stack pointed at a paragraph, not at the switcher.

I do not disable hydration to hide this. SSR is the point of the frontend. If the two passes cannot agree on the locale, they cannot agree on anything that depends on it. A production build that swallows the warning still ships the flash. The test is a Russian URL in a German browser, first paint, header language.

What I took from this

I watch the first hydration mismatch and the first paint of the header, not whether clicks still work. A page that answers clicks can still have thrown the tree.

I do not trust navigator.language or Accept-Language before mount. The URL prefix is the locale. Theme and time zone wait until after hydrate.

The rule I keep: one locale string in the page state, read before the first client render. If the warning points at a paragraph, check the article HTML. Do not turn hydration off.

Back to notes