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

Vue Best Practices & Production Patterns

Understanding Vue's API is the foundation. Using it well in production requires knowing the patterns that scale and the mistakes that cost you later.

TypeScript in Vue 3

Vue 3 has first-class TypeScript support. Enable it in <script setup>:

<script setup lang="ts">
import { ref } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

const user = ref<User | null>(null)
const users = ref<User[]>([])
</script>

Type your props and emits:

<script setup lang="ts">
interface Props {
  title: string
  count?: number
}

const props = defineProps<Props>()
const emit = defineEmits<{
  update: [value: string]
  close: []
}>()
</script>

Performance Patterns

v-once for static content that never changes:

<template>
  <h1 v-once>{{ staticTitle }}</h1>
</template>

v-memo for expensive list items:

<template>
  <div v-for="item in list" :key="item.id" v-memo="[item.selected]">
    <!-- Only re-renders when item.selected changes -->
    <ExpensiveComponent :item="item" />
  </div>
</template>

Lazy-load routes to reduce initial bundle size:

const routes = [
  {
    path: '/dashboard',
    component: () => import('@/views/Dashboard.vue') // lazy loaded
  }
]

Error Handling

Global error handling for unhandled errors:

// main.ts
app.config.errorHandler = (error, instance, info) => {
  console.error('Global error:', error)
  // Send to error tracking service (Sentry, etc.)
}

Local error handling with onErrorCaptured:

<script setup>
import { onErrorCaptured } from 'vue'

onErrorCaptured((error) => {
  console.error('Child error captured:', error)
  return false // prevent propagation
})
</script>

Async Components

For components that are large or need async data before rendering:

import { defineAsyncComponent } from 'vue'

const HeavyChart = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  loadingComponent: Spinner,
  errorComponent: ErrorMessage,
  delay: 200, // show loading after 200ms
  timeout: 3000
})

Testing Vue Components

Write tests with Vitest and Vue Testing Library:

import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import Counter from './Counter.vue'

test('increments count on click', async () => {
  render(Counter)
  
  const button = screen.getByRole('button', { name: /count/i })
  await userEvent.click(button)
  
  expect(screen.getByText('Count: 1')).toBeInTheDocument()
})

Project Structure

A scalable Vue project structure:

src/
  components/     # Reusable UI components
  composables/    # Reusable logic (useCounter, useFetch, etc.)
  stores/         # Pinia stores
  views/          # Page-level components (one per route)
  router/         # Vue Router configuration
  types/          # TypeScript interfaces and types
  utils/          # Pure utility functions

Keep views thin — they compose components and connect stores. Keep business logic in composables and stores.