Back to Blog
Frontend Development

Vue.js TypeScript Best Practices: Complete Guide for Modern Development

Claude Directory November 30, 2025
0 views

Master Vue.js with TypeScript using proven best practices for props, state, composables, Pinia, and more. Build scalable apps with type safety and clean code.

Getting Started with Vue.js and TypeScript

Vue.js paired with TypeScript is a powerhouse for building robust, maintainable front-end applications. TypeScript adds static type checking, catching errors early and improving developer experience through IntelliSense and refactoring support. This guide walks you through essential best practices, from project setup to advanced patterns, ensuring your code is scalable and production-ready. We'll use Vite for fast development and include practical examples you can copy-paste into your projects.

1. Project Setup with Vite

Start every new Vue.js TypeScript project with Vite—it's the official build tool, lightning-fast, and supports TypeScript out of the box. Skip the headaches of manual configuration.

Step-by-step setup:

  1. Install the Vue CLI alternative: Run npm create vue@latest my-vue-ts-app -- --template vue-ts.
  2. Navigate to your project: cd my-vue-ts-app.
  3. Install dependencies: npm install.
  4. Add popular libraries for a solid foundation:
    • UI: shadcn-vue (GitHub)
    • Icons: unplugin-icons
    • Utilities: class-variance-authority (CVA) for Tailwind variants

Pro tip: Use the official Vue TypeScript template for pre-configured linting with ESLint and Prettier. This enforces consistent code style across your team. Example vite.config.ts:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
})

Real-world application: For enterprise apps, integrate this setup with Vitest for testing: npm install -D vitest @vitest/ui and add to vite.config.ts.

2. Typing Components with defineProps and defineEmits

Forget generic props: any—use Vue 3's defineProps and defineEmits for fully typed components. This provides autocomplete and compile-time validation.

Key rules:

  • Always use the shorthand inference syntax with <script setup>.
  • Define props with readonly arrays/objects to prevent mutations.
  • Use generics for complex types.

Example: Typed Button Component

<script setup lang="ts">
import type { ButtonVariant } from './types'

const props = defineProps<{
  variant?: ButtonVariant
  size?: 'sm' | 'md' | 'lg'
}>()

const emit = defineEmits<{
  (e: 'click', id: number): void
}>()
</script>

<template>
  <button :class="['btn', props.variant]" @click="emit('click', 123)">
    {{ $slots.default?.() }}
  </button>
</template>

Adding value: For validation, wrap with withDefaults:

const props = withDefaults(defineProps<{
  label?: string
}>(), {
  label: 'Click me'
})

This pattern scales to forms, modals, and reusable UI kits. In a dashboard app, type all props to avoid runtime surprises.

3. Reactive State: ref, reactive, and computed

Vue's reactivity system shines with TypeScript. Use ref for primitives/objects, reactive for objects only, and computed for derived state.

Best practices:

  • Prefer ref with ! non-null assertion for known values.
  • Use readonly for props-derived refs.
  • Chain computed for performance.

Practical example: Counter with computed total

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

const count = ref(0)
const double = computed(() => count.value * 2)

const increment = () => count.value++
</script>

<template>
  <div>
    Count: {{ count }} | Double: {{ double }}
    <button @click="increment">+</button>
  </div>
</template>

Real-world tip: In lists, use computed for filtered/sorted data: const filteredUsers = computed(() => users.value.filter(u => u.active)). This keeps your template clean and reactive.

4. Composables: Reusable Logic

Composables are Vue's answer to React hooks—pure functions returning reactive state. They're perfect for custom logic like API fetches or form handling.

Guidelines:

  • Name with use prefix (e.g., useFetch).
  • Export refs directly.
  • Handle lifecycle with onMounted/onUnmounted.

Example: useCounter Composable

// composables/useCounter.ts
import { ref, type Ref } from 'vue'

export function useCounter(initial: number = 0): { count: Ref<number>, increment: () => void } {
  const count = ref(initial)
  const increment = () => count.value++
  return { count, increment }
}

Usage in component:

<script setup lang="ts">
import { useCounter } from '@/composables/useCounter'
const { count, increment } = useCounter(10)
</script>

Enhancement: Integrate VueUse (GitHub) for 200+ battle-tested composables like useStorage or useDebounce. For a todo app, create useTodos with persistence.

5. State Management with Pinia

Pinia is the modern store solution—lightweight, TypeScript-first, and devtools-friendly. Ditch Vuex.

Setup: npm install pinia, add to main.ts:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())

Store example: User Store

// stores/user.ts
import { defineStore } from 'pinia'
import type { User } from '@/types'

export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  const fetchUser = async (id: string) => {
    user.value = await api.getUser(id)
  }
  return { user, fetchUser }
})

Pro patterns:

  • Use defineStore('id', () => {}) for setup stores (recommended).
  • $subscribe for side effects.
  • Plugins for persistence.

In e-commerce apps, manage cart state here for global access.

6. Vue Router with Typed Routes

Type-safe routing prevents typos and 404s. Use vue-router with path-to-regexp types.

Install: npm install vue-router@4. Typed router setup in router/index.ts:

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  { path: '/users/:id', component: UserView }
]

export const router = createRouter({
  history: createWebHistory(),
  routes
})

Infer types: Use RouteLocationRaw and RouterOptions for full safety.

Navigation guards: Protect routes with beforeEach:

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isLoggedIn()) next('/login')
  else next()
})

7. Advanced Patterns: defineModel and Suspense

defineModel (Vue 3.4+): Simplifies v-model on components.

<script setup lang="ts">
const modelValue = defineModel<boolean>({ required: true })
</script>

<template>
  <input v-model="modelValue" type="checkbox" />
</template>

Reference: RFC

Suspense for async components: Wrap lazy-loaded parts:

<Suspense>
  <template #default><AsyncComponent /></template>
  <template #fallback>Loading...</template>
</Suspense>

8. Linting and Tooling

Enforce best practices:

  • ESLint: vue/eslint-config-typescript.
  • Prettier for formatting.
  • @vue/eslint-config-prettier.

.eslintrc.js:

module.exports = {
  extends: [
    '@vue/eslint-config-typescript',
    '@vue/eslint-config-prettier'
  ]
}

Final Tips for Production

  • Use unplugin-vue-components for auto-imports.
  • Environment variables: VITE_API_URL.
  • Testing: Vitest + Vue Test Utils.
  • Deployment: Vite build to static hosting.

Follow these practices, and your Vue.js TypeScript apps will be type-safe, performant, and a joy to maintain. Experiment in a sandbox project today!

<div style="text-align: center; margin-top: 2rem;"> <a href="https://cursor.directory/vuejs-typescript-best-practices" target="_blank" rel="noopener noreferrer" class="view-full-resource-btn" style="display: inline-block; background-color: #f97316; color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; transition: background-color 0.2s;">View Full Resource</a> </div>
GitHub Project

Comments

More Blog

View all
Claude for Developers

Building Voice Agents with Claude API and ElevenLabs: Conversational AI Guide

Build natural voice agents combining Claude API's superior reasoning with ElevenLabs' lifelike TTS. This end-to-end guide creates a conversational web app with STT, AI chat, and speech synthesis.

C
Claude Directory
2
Model Comparisons

Claude vs Mistral Large 2: 2025 Data Analysis Benchmarks and Use Cases

As data volumes explode in 2025, choosing between Claude's reasoning depth and Mistral Large 2's efficiency is critical. We benchmark SQL generation, visualizations, and large datasets to reveal the w

C
Claude Directory
1
Enterprise

Claude Enterprise for Cybersecurity: Threat Modeling and Incident Response

In the high-stakes world of cybersecurity, rapid threat modeling and incident response can mean the difference between containment and catastrophe. Discover how Claude Enterprise empowers security tea

C
Claude Directory
1
Claude Code

Claude Code in VS Code: Custom Commands for Refactoring Large Codebases

Refactoring sprawling codebases manually? Harness Claude Code's power in VS Code with custom commands to automate AI-driven refactors across TypeScript and Python projects—saving hours of drudgery.

C
Claude Directory
1
Claude for Developers

Claude SDK Rust for Blockchain: Smart Contract Auditing Agents

Build blazing-fast smart contract auditing agents in Rust using the Claude SDK. Harness Claude's reasoning to scan Solidity code for vulnerabilities like reentrancy and overflows.

C
Claude Directory
1
Claude Best Practices

Advanced Claude Artifacts: Collaborative Editing in Multi-User Sessions

Elevate team productivity with Claude Artifacts in multi-user projects—enable real-time iterative editing for code reviews and docs without leaving the interface.

C
Claude Directory
1