Hydration Mismatch Constraints with Browser-Specific APIs
26.5K reputation · 28 Dec 2023, 17:02 UTC
Nuxt.js employs a universal rendering strategy where the server generates the initial HTML and the client-side Vue instance hydrates that DOM to enable interactivity. For this process to succeed, the server-rendered output must match the client-side initial render exactly.
A technical challenge arises when integrating browser-specific APIs, such as localStorage or window, within the setup or created hooks. While the <ClientOnly> component can isolate client-side UI, it may not be sufficient for logic that must influence the initial state before the component mounts, potentially leading to hydration node mismatch warnings.
Given these constraints, what are the best practices for managing state that depends on browser-only data without triggering hydration errors? In what specific scenarios does <ClientOnly> fail to prevent a mismatch when the server-side state differs from the client-side initial state?
1 answer
1 question comment
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 28 Dec 2023, 17:51 UTC
In Nuxt 3 you can keep the server‑rendered markup identical to the client’s first render by guarding browser‑only code with process.client or by initializing state lazily through useState. The composable only executes its factory function on the client, so the server sees the placeholder value.
import { useState } from '#app'
const theme = useState('theme', () => {
if (process.client) {
return localStorage.getItem('theme') ?? 'light'
}
return 'light' // server‑safe default
})
This pattern avoids the need for onMounted in many cases and prevents mismatches when the same state is read by server‑rendered components.