Skip to content

Nuxt State Management — useState and Pinia in Nuxt 3

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Nuxt State Management. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Nuxt 3 state management: useState for simple shared state and Pinia for complex state management with stores.

In this lesson, you'll understand how to share state across components with useState, and how to use Pinia for more structured state management.

What You'll Learn

How to use useState for SSR-safe shared state, install and configure Pinia, create Pinia stores, and use them in components.

Why It Matters

State management keeps your application data consistent across components. Nuxt provides both simple (useState) and advanced (Pinia) solutions.

flowchart TD
    A[State Management] --> B[Simple State]
    A --> C[Complex State]
    B --> D[useState]
    C --> E[Pinia]
    D --> F[useState('key', () => initial)]
    E --> G[defineStore]
    E --> H[Actions, Getters]
    style A fill:#00dc82,color:#fff

useState

Share reactive state across components with SSR support:

// composables/useCart.ts
export const useCart = () => {
  const cart = useState('cart', () => []);

  const addItem = (item) => {
    cart.value = [...cart.value, item];
  };

  const removeItem = (id) => {
    cart.value = cart.value.filter(i => i.id !== id);
  };

  const total = computed(() =>
    cart.value.reduce((sum, item) => sum + item.price, 0)
  );

  return { cart, addItem, removeItem, total };
};

Usage in any component:

<script setup>
const { cart, addItem, total } = useCart();
</script>

<template>
  <div>
    <p>Cart: {{ cart.length }} items ({{ total }})</p>
    <button @click="addItem({ id: 1, name: 'Widget', price: 10 })">Add</button>
  </div>
</template>

Output: The cart state is shared across all components. SSR serializes the state so it persists after hydration.

Pinia Setup

npm install @pinia/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt']
});

Creating a Pinia Store

// stores/counter.ts
export const useCounterStore = defineStore('counter', () => {
  // State
  const count = ref(0);

  // Getters
  const doubleCount = computed(() => count.value * 2);

  // Actions
  function increment() { count.value++; }
  function decrement() { count.value--; }
  function reset() { count.value = 0; }

  return { count, doubleCount, increment, decrement, reset };
});

Usage:

<script setup>
const counter = useCounterStore();
</script>

<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">+</button>
    <button @click="counter.decrement">-</button>
  </div>
</template>

Pinia with Async Actions

Store with server data fetching:

// stores/posts.ts
export const usePostsStore = defineStore('posts', () => {
  const posts = ref([]);
  const currentPost = ref(null);
  const loading = ref(false);
  const error = ref(null);

  async function fetchPosts() {
    loading.value = true;
    error.value = null;
    
    try {
      const data = await $fetch('/api/posts');
      posts.value = data;
    } catch (e) {
      error.value = e.message;
    } finally {
      loading.value = false;
    }
  }

  async function fetchPost(id: string) {
    loading.value = true;
    try {
      const data = await $fetch(`/api/posts/${id}`);
      currentPost.value = data;
    } catch (e) {
      error.value = e.message;
    } finally {
      loading.value = false;
    }
  }

  return { posts, currentPost, loading, error, fetchPosts, fetchPost };
});

useState vs Pinia

Feature useState Pinia
Complexity Simple key-value Full store
DevTools Limited Vue DevTools
SSR Built-in Built-in
TypeScript Manual typing Auto-generated
Best for Small shared state Complex app state

Common Mistakes

  1. Using ref instead of useState for shared state: ref is local to the component. Use useState for state shared across components.
  2. Not using a unique key in useState: The string key must be unique. Collisions cause unexpected shared state.
  3. Mutating Pinia state directly: Use actions to modify state. Direct mutation works but bypasses DevTools tracking.
  4. Forgetting @pinia/nuxt module: Without it, Pinia stores don't work in Nuxt. Install and add to modules.
  5. Not using Pinia's $reset for forms: Pinia stores don't have a built-in reset. Implement one manually if needed.

Practice Questions

  1. What is the difference between useState and ref? Answer: useState is shared across components and SSR-safe. ref is local to a single component instance.

  2. How do you define a Pinia store with the Composition API? Answer: Use defineStore('name', () => { ... }) with a setup function that returns state, getters, and actions.

  3. How do you use a Pinia store in a component? Answer: Call const store = useStoreName() inside <script setup>. Access state as store.property and actions as store.action().

  4. When should you use Pinia instead of useState? Answer: For complex state with multiple actions, getters, async operations, or when you need Vue DevTools integration.

Challenge

Build a shopping cart with Pinia: add/remove items, quantity updates, coupon application, and tax calculation. Use getters for subtotal, tax, and total. Persist to localStorage.

Mini Project

Create a full-featured e-commerce storefront with: Pinia stores for cart, user auth, product catalog, and wishlist. Use useState for theme toggling. Connect actions to server routes.

FAQ

Does Pinia work with Nuxt 3's auto-imports?

: Yes. Pinia stores in stores/ are auto-imported. Call useStoreName() without imports.

Can I use Pinia with TypeScript?

: Yes. Pinia has excellent TypeScript support. Define typed stores and get full IDE autocompletion.

How do I persist Pinia state?

: Use pinia-plugin-persistedstate or manually sync state to localStorage in a watch.

Does Pinia support SSR hydration?

: Yes. Pinia works with Nuxt's SSR. State from server is serialized and hydrated on the client.

What's Next

Learn about Nuxt SEO and Meta Tags to optimize your Nuxt application for search engines.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro