Why You Should Not Use Global State Composable in Nuxt
Using global state composables in Nuxt 3 may seem convenient, but they come with hidden pitfalls, especially in SSR (server-side rendering). Here’s why you should avoid them and what to use instead.
Key Issues with Global State Composables
State Leakage in SSR
Global state composables store data outside of request scopes, causing unintended data sharing between users if modified on the server.
Example: Shared State Across Requests
// app/composables/useCounter.ts
const count = ref(0);
export const useCounter = () => {
const increment = () => count.value++;
return { count, increment };
};
<script setup lang="ts">
const { count, increment } = useCounter();
// Modify state on the server
count.value++;
</script>
<template>
<div>
<div>Count: {{ count }}</div>
<Button @click="increment">Increment</Button>
</div>
</template>
If modified on the server,
count
persists across users, leading to state leakage.
Inconsistent Behavior Between Server and Client
Since global state is reused across requests, it behaves differently in SSR and client-side, causing hydration mismatches and unexpected resets.
Debugging Challenges
State persistence across requests makes debugging unpredictable and difficult.
Memory Leaks
Global state can accumulate over time, leading to increased memory usage.
Example: Memory Leak
// app/composables/useFeed.ts
const posts = ref([]);
export const useFeed = () => {
const addPost = (post) => posts.value.push(post);
return { posts, addPost };
};
If used globally, posts
keeps growing across requests, consuming more memory.
Better Alternatives
Use useState()
for Request-Scoped State
Ensures fresh state per request, preventing SSR issues.
// app/composables/useCounter.ts
export const useCounter = () => {
const count = useState("count", () => 0);
const increment = () => count.value++;
return { count, increment };
};
Use Pinia for Structured Global State
Pinia ensures proper SSR state handling and avoids memory leaks.
// app/stores/counter.ts
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
});
Conclusion
❌ Avoid global state composables due to:
- State leakage if modified on the server.
- Inconsistent SSR behavior.
- Difficult debugging.
- Memory leaks.
✅ Instead, use useState() for scoped state or Pinia for structured state management. This ensures efficient, predictable state handling in Nuxt 3. 🚀