Vue 3 + TypeScript Design Guide
A standard template is enough to start
npm create vite@latest my-app -- --template vue-ts
cd my-app
npm install
Early in a project, it is usually more important to build the habit of separating components from domain models under strict mode than to keep expanding configuration.
Props and emits are the core of the component contract
<script setup lang="ts">
interface Props {
title: string
disabled?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
submit: [id: number]
}>()
</script>
This style makes the contract between parent and child explicit in the type system. It is especially useful because event payloads become clear, which makes interaction flow easier to read.
Choose ref and reactive by purpose
ref is intuitive for primitive values or values that are clearly replaced as a whole. reactive can be more convenient when several related fields need to be handled together. Still, because destructuring can break reactivity, you need to understand the usage context before reaching for it.
Do not mix API types and UI types directly
If you use backend response models directly as screen component types, the frontend becomes tightly coupled to the response shape. It is more resilient to separate API DTOs, screen view models, and form input models when needed.
Responsibility comes before typing in composables
TypeScript is powerful, but that does not mean every piece of logic should become a generic composable. It is usually better to abstract only when a responsibility is truly repeated. The type system should support that responsibility safely, not become the reason the abstraction exists.
Wrap-up
The core of Vue 3 + TypeScript is not advanced type tricks, but stable contracts. If you think separately about props, emits, composables, and state models, types stop feeling like rules that slow development down and start acting like interfaces that help teams collaborate.
What Gets Hard in Production
- Template inference can look safe while store, composable, and API boundaries still leak implicit
anythrough the app. - Type safety gets weaker when props, emits, and async data shapes are inferred differently in every component.
- A Vue codebase becomes hard to navigate when shared types and composables have no layering rules.
Architecture Decisions That Matter
- Use strict TypeScript together with Volar-friendly conventions for props, emits, and composables.
- Split transport, domain, and presentation models instead of letting
refandreactivehold raw API payloads everywhere. - Treat composables as public APIs and keep their return types stable and intentionally shaped.
Practical Example
A good baseline is to normalize server payloads before they enter reactive state:
type ProductResponse = {
id: string
product_name: string
stock_count: number
}
type ProductViewModel = {
id: string
name: string
isSoldOut: boolean
}
function toProductViewModel(product: ProductResponse): ProductViewModel {
return {
id: product.id,
name: product.product_name,
isSoldOut: product.stock_count <= 0,
}
}
Anti-Patterns to Avoid
- Keeping raw API objects in component state and mutating them across the template tree.
- Using broad
Record<string, unknown>types where the domain is actually known. - Creating composables whose return shape changes screen by screen.
Operational Checklist
- Run type checking in CI independently from the build step.
- Keep
definePropsanddefineEmitssignatures explicit at shared component boundaries. - Review store and composable contracts whenever API schema changes.
- Track editor and CI type latency once the project grows.
Final Judgment
Vue 3 with TypeScript works best when types reinforce composable boundaries and component contracts. Template inference alone is not enough for long-term architectural safety.
Continue Reading
Related posts
Vue.js Architecture Design Guide
This guide explains how to design a Vue.js project as a maintainable frontend system rather than just a collection of components, covering state boundaries, routing, data flow, folder structure, performance, and collaboration.
🖥️ FrontendVue Component Design Principles Guide
This guide covers component responsibility, reuse boundaries, props and events design, and slot strategy in Vue applications from a practical engineering perspective.
💬 LanguageTypeScript Utility Types: A Practical Guide
A production-focused guide to TypeScript utility types. Learn how to model DTOs, update payloads, selectors, and derived types without making your type layer harder to read.
💬 LanguageTypeScript Generics: A Practical Guide
A practical and production-focused guide to TypeScript generics. Learn when generics improve API contracts, when they overcomplicate code, and how to keep inference readable.
Next Path