Laravel 12 + Vue 3 + Vite + Pinia: Ultimate SPA State Management Guide

Author

Kritim Yantra

Jul 15, 2025

Laravel 12 + Vue 3 + Vite + Pinia: Ultimate SPA State Management Guide

Imagine this: You're building a real-time dashboard with Laravel and Vue. Data flows everywhere—user preferences, API responses, UI state. How do you keep everything in sync without spaghetti code?

Enter Pinia �—the official Vue state management library that makes handling complex state simple and scalable.

In this guide, you’ll learn how to:
Set up Laravel 12 + Vue 3 + Vite (optimized for speed)
Integrate Pinia for flawless state management
Build a real-world example (authentication + dynamic data)


🛠Step 1: Laravel 12 + Vue 3 + Vite Setup

(Already familiar? Jump to Step 2 for Pinia!)

1. Create a Laravel Project

composer create-project laravel/laravel laravel-pinia-spa
cd laravel-pinia-spa

2. Install Vue 3 & Vite

npm install vue@next @vitejs/plugin-vue
npm install --save-dev vite laravel-vite-plugin

3. Configure Vite (vite.config.js)

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [
    laravel(['resources/js/app.js']),
    vue(),
  ],
});

4. Set Up Vue 3 (resources/js/app.js)

import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

5. Update Blade Template (resources/views/welcome.blade.php)

<!DOCTYPE html>
<html>
<head>
  @vite(['resources/js/app.js'])
</head>
<body>
  <div id="app"></div>
</body>
</html>

🚀 Test it: Run npm run dev and visit http://localhost:8000.


🔥 Step 2: Setting Up Pinia

1. Install Pinia

npm install pinia

2. Initialize Pinia in app.js

import { createApp } from 'vue';
import { createPinia } from 'pinia'; // <-- Add this
import App from './App.vue';

const app = createApp(App);
app.use(createPinia()); // <-- Use Pinia
app.mount('#app');

3. Create Your First Store (stores/counter.js)

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

4. Use the Store in a Vue Component

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

<script setup>
import { useCounterStore } from '@/stores/counter';
const counter = useCounterStore();
</script>

💡 Why Pinia?
Simpler syntax than Vuex
TypeScript support out of the box
Devtools integration for debugging


🌐 Step 3: Real-World Example (Auth + API State)

1. Create an Auth Store (stores/auth.js)

import { defineStore } from 'pinia';
import axios from 'axios';

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null,
    token: localStorage.getItem('token') || null,
  }),
  actions: {
    async login(email, password) {
      const res = await axios.post('/api/login', { email, password });
      this.user = res.data.user;
      this.token = res.data.token;
      localStorage.setItem('token', this.token);
    },
    logout() {
      this.user = null;
      this.token = null;
      localStorage.removeItem('token');
    },
  },
});

2. Protect Routes with Pinia

<script setup>
import { useAuthStore } from '@/stores/auth';
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';

const auth = useAuthStore();
const router = useRouter();

onMounted(() => {
  if (!auth.token) router.push('/login');
});
</script>

3. Fetch Data with Pinia (stores/posts.js)

import { defineStore } from 'pinia';
import axios from 'axios';

export const usePostsStore = defineStore('posts', {
  state: () => ({
    posts: [],
    loading: false,
  }),
  actions: {
    async fetchPosts() {
      this.loading = true;
      const res = await axios.get('/api/posts');
      this.posts = res.data;
      this.loading = false;
    },
  },
});

🚀 Step 4: Optimizing Performance

1. Persist State with pinia-plugin-persistedstate

npm install pinia-plugin-persistedstate

Configure in app.js:

import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);

Make a store persistent:

defineStore('auth', {
  persist: true, // <-- Add this
  state: () => ({ ... }),
});

2. Hydrate State from Laravel

In your Blade file:

<script>
  window.__INITIAL_STATE__ = @json(auth()->user() ? ['auth' => ['user' => auth()->user()]] : []);
</script>

Then in app.js:

if (window.__INITIAL_STATE__) {
  pinia.state.value = window.__INITIAL_STATE__;
}

🎉 Conclusion & Next Steps

You’ve now built a high-performance SPA with:
Laravel 12 API backend
Vue 3 + Vite for lightning-fast frontend
Pinia for scalable state management

🚀 Try These Next:

  • Add real-time updates with Laravel Echo
  • Implement Vue transitions for smoother UX
  • Explore Pinia + TypeScript for strict typing

💬 Let’s Discuss:
What’s your go-to state management solution in Vue? Pinia, Vuex, or something else?


FAQs

Q1: Can I use Pinia with Vue 2?

Yes! Install pinia@v2 for Vue 2 compatibility.

Q2: How does Pinia compare to Vuex?

📌 Pinia is the official successor—simpler API, better TS support, no mutations!

Q3: Does Pinia work with SSR?

Yes! Perfect for Nuxt.js or Laravel + Inertia SSR.


🔥 Your Turn: Share your Pinia experiences below! Struggles? Wins? Let’s chat. 👇

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts