Vue.js TypeScript Best Practices: Complete Guide for Modern Development
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:
- Install the Vue CLI alternative: Run
npm create vue@latest my-vue-ts-app -- --template vue-ts. - Navigate to your project:
cd my-vue-ts-app. - Install dependencies:
npm install. - 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
readonlyarrays/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
refwith!non-null assertion for known values. - Use
readonlyfor props-derived refs. - Chain
computedfor 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
useprefix (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). $subscribefor 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-componentsfor 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>Comments
More Blog
View allBuilding 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.
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
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
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.
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.
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.