Managing Browser-Only State without Hydration Errors
To prevent hydration mismatches when using browser-specific APIs (like localStorage or window), you must ensure that the server-rendered HTML and the client's first render are identical. The most effective practice is to defer the assignment of browser-dependent state until after the component has mounted.
Recommended Implementation Patterns
Instead of accessing browser APIs in setup() or created(), move the logic into the onMounted hook. This ensures the code only executes on the client after the initial DOM structure is established.
<script setup>
import { ref, onMounted } from 'vue'
const userTheme = ref('light') // Default server-safe value
onMounted(() => {
// This runs only on the client, after hydration
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
userTheme.value = savedTheme
}
})
</script>
When <ClientOnly> Fails to Prevent Mismatches
The <ClientOnly> component prevents the server from rendering its children entirely. However, it fails to prevent mismatches in the following scenarios:
- Shared State Logic: If a
<ClientOnly> component updates a global state (like a Pinia store) that is also used by a server-rendered component, the server-rendered component will have a different initial value than what the client expects after the store update.
- Conditional Wrapper Logic: If the decision to show the
<ClientOnly> component itself depends on a browser API (e.g., v-if="isMobile" where isMobile is calculated via window.innerWidth), the server will render the false branch, while the client renders the true branch.
- Layout Shifts: While it may stop the console warning,
<ClientOnly> can cause a "flash of unstyled content" or a layout jump because the server renders nothing, and the client suddenly injects a DOM node after hydration.
Verification and Debugging
To verify if a mismatch is occurring, you can use these scoped methods:
- Disable JavaScript: Use browser DevTools to disable JS. The resulting page is exactly what the server sent. Compare this to the page with JS enabled to spot "jumping" elements.
- Console Inspection: Look for
Hydration node mismatch warnings in the browser console, which typically point to the specific HTML tag where the divergence occurred.
Diagnostic Note: Are you using a global state management library (like Pinia) to sync this browser data across multiple components? If so, the solution may require a client-side plugin rather than component-level hooks.