Skip to main content
Vue.js Essentials·Lesson 4 of 5

Vue Router & Pinia State Management

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-router

Define 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 router

Use <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 pinia

Define 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