Build1 publisher3 min readPublished
A random tip picked from three strings survives Nuxt hydration one request in three
A dev.to walkthrough's tip-of-the-day widget is correct JavaScript and still logs a hydration text mismatch, because Nitro and the browser each draw their own index and Vue replaces the node where the two disagree.
The Engineer · Build desk

What happened
- A dev.to walkthrough shows a Nuxt tip-of-the-day widget whose console logs a hydration text mismatch: the server rendered the tip about Nitro, the client rendered the tip about useAsyncData.
- Nuxt renders the same component tree twice in two different environments, then asks the second render to adopt the DOM the first render already produced instead of rebuilding it from scratch.
- The article reports the same failure with new Date(), with window.innerWidth and with anything read from localStorage inside the render path, because the value depends on where the code runs.
- The walkthrough is verified against the Nuxt v4.5 release line of August 2026 and says everything in it also applies to Nuxt 3 running compatibilityVersion: 4.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The render path is an environment with less available to it than the browser: the server pass runs in Node with no DOM. So any template expression that reads the clock, the viewport or local storage has to be moved or fenced before its node can keep its listeners.
- exposure Whatever the mismatched node held is what the user gets wrong for a moment: the article's point is that a price or an in-stock flag shown in the first paint and swapped on boot arrives as a bug report.
- decision Reviewing for correctness will not catch this class of bug, because the component compiles and runs; the standard has to become where each value is computed.
- cost The debugging cost lands on whoever owns the page: the walkthrough treats mapping a warning back to the line that caused it as a skill it has to teach.
Hydration mode is where the damage happens. The client bundle does not create fresh nodes the way a client-only SPA would. It walks the DOM the server produced, node by node, and attaches reactivity and event listeners to what is already there [6]. That walk assumes the two renders line up. When the text does not match, Vue tears out the node and builds a new one. The listener on the old node goes with it. The dev.to walkthrough describes the visible result as a button the user already clicked that stops responding [7].
The tip widget makes the odds countable. Three strings, one index from `Math.floor(Math.random() * TIPS.length)`, evaluated once in Node and once in the browser [1]. If both draws are uniform and independent, they agree one time in three. Two requests in three paint a paragraph whose text is swapped out as soon as the bundle boots [15]. The warning quoted in the article shows one such pair. The server rendered "Tip of the day: Nitro is just Node under the hood." and the client rendered "Tip of the day: Use useAsyncData for anything that fetches." [2] Nothing crashes, and the page still works [17].
"the bug isn't in what you wrote, it's in the fact that Nuxt runs what you wrote twice, in two different places, and bets your app's interactivity on both runs agreeing," the walkthrough says [8]. Nuxt does carry one class of value across the boundary intact. Nitro serializes the result of every `useAsyncData` and `useFetch` call and every `useState` into a `<script id="__NUXT_DATA__">` block in the page [4]. A plain `const` in `<script setup>` is not on that list, so the browser evaluates it again and gets its own answer [16].
Whether this reaches a given codebase has little to do with its size. The condition is narrow: one expression in the render path reads something the Node process does not have, such as a DOM or a browser [4]. One of the three strings in the example is "Nitro is just Node under the hood." [1] That is also the reason the server has nothing to read. Pages that compute their templates from props and from fetched data will not produce this warning. I would put the review question as placement: for each value in a template, can the server compute it, and will the browser compute the same thing.
The walkthrough names three fixes to choose between per situation: `onMounted`, `<ClientOnly>`, and `data-allow-mismatch` [12]. Among its stated objectives is avoiding a fix that "looks reasonable but guarantees a mismatch every time" [14]. The text available ends mid-sentence in the hydration-mode step, before those sections [13].
What to watch
- The rest of the dev.to piece: which reasonable-looking fix it says guarantees a mismatch every time.
- What data-allow-mismatch actually suppresses, since it sits in the same fix list as onMounted and <ClientOnly>.
- Whether Nuxt 3 projects running compatibilityVersion: 4 reproduce the same warning text.