Most Vue applications need two things beyond components: routing (navigating between pages) and state management (sharing data across components). Vue Router and Pinia are the official solutions for both.
Vue Router
Install Vue Router:
npm install vue-routerDefine your routes:
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import UserProfile from '@/views/UserProfile.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/users/:id', component: UserProfile },
]
})
export default routerUse <RouterLink> for navigation and <RouterView> to render the matched component:
<template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<RouterView />
</template>Dynamic Routes and Params
Access route parameters in components:
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const userId = route.params.id // from /users/:id
</script>Route Guards
Protect routes from unauthorized access:
router.beforeEach((to, from) => {
const isAuthenticated = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
return { path: '/login' }
}
})// Mark routes as requiring auth
{ path: '/dashboard', component: Dashboard, meta: { requiresAuth: true } }Pinia — State Management
Pinia is Vue's official state management library. It's simpler than Vuex and fully TypeScript-compatible.
Install:
npm install piniaDefine a store:
// stores/useUserStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isLoggedIn = computed(() => !!user.value)
async function login(email: string, password: string) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
user.value = await response.json()
}
function logout() {
user.value = null
}
return { user, isLoggedIn, login, logout }
})Use the store in any component:
<script setup>
import { useUserStore } from '@/stores/useUserStore'
const userStore = useUserStore()
</script>
<template>
<div v-if="userStore.isLoggedIn">
Welcome, {{ userStore.user.name }}
<button @click="userStore.logout">Logout</button>
</div>
</template>When to Use Pinia vs Local State
Use local state (ref/reactive in a component) when the state is only relevant to that component or its children.
Use Pinia when:
- Multiple unrelated components need the same data
- Data needs to persist across route changes
- You need to share state between deeply nested components without prop drilling