Copied to Clipboard
On the server, localStorage doesn’t exist, so Nuxt either errors out or renders a fallback value. Then on the client, the value changes → hydration mismatch.
b) Non-Deterministic Data
Anything that changes between server and client — e.g., Date.now(), Math.random() — will cause mismatches if used directly in state.
c) Forgetting to Persist State Between Navigations
If your store resets on each navigation, you’ll lose state (like user preferences, auth tokens, or cart data).
🟢 Using Pinia with Nuxt
Nuxt comes with first-class Pinia support via the @pinia/nuxt module. This takes care of serializing and deserializing store state between server and client.
Install:
npm install pinia @pinia/nuxt
Add to nuxt.config.ts:
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
],
pinia: {
autoImports: ['defineStore', 'storeToRefs'],
},
})
4. Persisting State with Plugins
Pinia itself doesn’t persist state automatically. But you can add a plugin:
// plugins/persistedState.client.ts
import { defineNuxtPlugin } from '#app'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
export default defineNuxtPlugin(({ $pinia }) => {
$pinia.use(piniaPluginPersistedstate)
})
Then in your store:
export const useUserStore = defineStore('user', {
state: () => ({
theme: 'light' as 'light' | 'dark',
}),
persist: true, // 👈 stored in localStorage by default
})
Now your state survives page reloads and client-side navigation.
5. Avoiding SSR Mismatches with TypeScript
When defining state with TypeScript, always provide initial values that are safe for SSR:
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as { id: string; qty: number }[],
initialized: false,
}),
actions: {
init() {
if (process.client) {
this.items = JSON.parse(localStorage.getItem('cart') || '[]')
}
this.initialized = true
},
},
})
Key takeaways:
- Always check
process.client before using browser APIs.
- Initialize state with SSR-safe defaults.
- Hydrate additional data only after the client takes over.
✅ Best Practices
- Use SSR-safe defaults for all store state.
- Wrap client-only logic in
if (process.client) blocks.
- Persist critical state (auth, preferences, cart) with
pinia-plugin-persistedstate.
- Consider server-side fetching for initial state, then hydrate client-only extras later.
- Use TypeScript to strictly type your stores, avoiding accidental
undefined or any values that cause mismatch.
📖 Learn more
If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below:
Vue School Link
It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉
🧪 Advance skills
A certification boosts your skills, builds credibility, and opens doors to new opportunities. Whether you're advancing your career or switching paths, it's a smart step toward success.
Check out Certificates.dev by clicking this link or by clicking the image below:
Certificates.dev Link
Invest in yourself—get certified in Vue.js, JavaScript, Nuxt, Angular, React, and more!
✅ Summary
State hydration is one of the most subtle pitfalls of SSR in Nuxt. By combining Pinia, Nuxt’s SSR integration, and TypeScript’s type safety, you can avoid hydration mismatches and ensure your app behaves consistently across server and client.
Next time you see [Vue warn]: Hydration completed but contains mismatches, check your store initialization and persistence setup first — chances are, the fix is there.
Take care!
And happy coding as always 🖥️