Skip to content
Learni
View all tutorials
Vue.js

How to Build an Expert Vue 3 App in 2026

Lire en français

Introduction

In 2026, Vue 3 leads the frontend world thanks to its Composition API, which revolutionizes code reusability and maintainability. Unlike the Options API, it lets you extract business logic into reusable composables, strict TypeScript typing, and seamless integration with tools like Pinia for state management and Vite for ultra-fast bundling. This expert tutorial guides you through building an advanced TODO app: guarded routing, local persistence, async fetching via JSONPlaceholder, and Suspense for loading states. You'll learn to scale a professional SPA while avoiding monolithic app pitfalls. By the end, you'll have a copy-paste-ready project optimized for production, SEO, and performance. Ideal for seniors striving for Vue excellence. (142 words)

Prerequisites

  • Node.js 20+ and npm 10+
  • Advanced Vue 3 knowledge (Options API, lifecycle hooks)
  • Intermediate TypeScript (generics, interfaces)
  • VS Code with Volar and TypeScript Vue extensions
  • Git for version control

Initialize the Vite + Vue TS Project

terminal
npm create vue@latest todo-expert -- --template vue-ts
cd todo-expert
npm install
npm install vue-router@4 pinia @vueuse/core @tanstack/vue-query
npm install -D @types/node
npm run dev

This command creates a Vite project with the Vue + TypeScript template, installs essential dependencies: Vue Router for routing, Pinia for persistent global state, VueUse for composable utilities, and TanStack Query for reactive data fetching. The --template vue-ts flag enables strict typing from the start. Run npm run dev to verify the app starts at http://localhost:5173.

Configure Vite and Plugins

Vite is Vue 3's default bundler, offering instant HMR and optimized builds. We'll extend its config to support aliases, API proxying, and strict TS resolutions—essential for a scalable expert app.

Complete vite.config.ts

vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      "@": resolve(__dirname, "src"),
    },
  },
  server: {
    proxy: {
      '/api': {
        target: 'https://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, '/'),
      },
    },
  },
})

This config sets up the @ alias for imports from src/, enables the Vue plugin, and proxies /api calls to JSONPlaceholder to simulate a real backend without CORS issues. The rewrite cleans up paths. Restart the dev server after changes to apply them.

Initialize Router and Pinia in main.ts

src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import router from './router'

const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

The main.ts bootstraps the Vue app, integrates Pinia for global state, and Vue Router for SPA navigation. createPinia() enables default persistence via plugins. This entry point is the heart of every expert Vue 3 app.

Set Up Advanced Routing

Vue Router 4 supports guards, lazy-loading, and nested routes. We'll configure two views: Home for TODOs and About, with a guard to protect routes based on authentication (simulated).

router/index.ts with Guards

src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('@/views/HomeView.vue'),
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('@/views/AboutView.vue'),
      beforeEnter: (to, from) => {
        const authStore = useAuthStore()
        if (!authStore.isAuthenticated) return '/'
      },
    },
  ],
})

export default router

This router uses createWebHistory for clean URLs, lazy-loads views to optimize the initial bundle, and implements a beforeEnter guard that checks auth via Pinia. Routes are implicitly typed by TS.

Pinia Store for Auth and Persistence

src/stores/auth.ts
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { useLocalStorage } from '@vueuse/core'

export const useAuthStore = defineStore('auth', () => {
  const token = useLocalStorage('auth-token', '')
  const isAuthenticated = ref(!!token.value)

  function login(newToken: string) {
    token.value = newToken
    isAuthenticated.value = true
  }

  function logout() {
    token.value = ''
    isAuthenticated.value = false
  }

  return { token, isAuthenticated, login, logout }
}, {
  persist: true,
})

This store uses defineStore with Composition API and @vueuse/core for local persistence via useLocalStorage. The login/logout actions mutate reactive state. The persist: true option auto-saves to sessionStorage.

Create Reusable Composables

Composables are Vue 3's superpower: pure functions exporting reactive state and logic. We'll create one for fetching TODOs with TanStack Query, integrated with Suspense.

Composable useTodos with Vue Query

src/composables/useTodos.ts
import { ref } from 'vue'
import { useQuery } from '@tanstack/vue-query'

interface Todo {
  id: number
  title: string
  completed: boolean
}

export function useTodos() {
  return useQuery({
    queryKey: ['todos'],
    queryFn: async (): Promise<Todo[]> => {
      const res = await fetch('/api/todos?_limit=5')
      if (!res.ok) throw new Error('Fetch failed')
      return res.json()
    },
  })
}

This composable wraps TanStack's useQuery for caching, auto-refetching, and states (loading/error). Typed with Todo interface, it fetches via the Vite proxy. Fully reusable, it boosts performance by avoiding unnecessary re-fetches.

HomeView Component with Suspense

src/views/HomeView.vue
<template>
  <div>
    <h1>Mes TODOs Experts</h1>
    <Suspense>
      <template #default>
        <TodoList />
      </template>
      <template #fallback>
        <div>Chargement des tâches...</div>
      </template>
    </Suspense>
    <router-link to="/about">About (protégé)</router-link>
  </div>
</template>

<script setup lang="ts">
import TodoList from '@/components/TodoList.vue'
</script>

This component uses to handle async states in children like TodoList (which uses the composable).

How to Build Expert Vue 3 App in 2026 | Learni