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. 👇
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
🛠 Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Flask Blog CMS with Admin Dashboard — Python Source Code
A Flask-based blogging website with a built-in admin dashboard. Manage articles, images, categories, tags and featured p...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.