
September 08, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
TypeScript with Vue 3 best practices turn a fast-moving front end into something your team can refactor without fear. Vue 3's Composition API, Pinia, and Vite 8.x make typed components practical on real projects. Yet many teams still treat types as optional decoration. That choice shows up later as broken builds, silent prop mismatches, and API contract bugs. This guide covers the setup, typing patterns, and CI checks I rely on when shipping Vue inside Laravel and Vue full-stack applications.
How do you set up TypeScript with Vue 3 for a new project in 2026?
Start from the official scaffold rather than bolting TypeScript onto a plain JavaScript project later. The Vue CLI era is largely behind us. Vite 8.x with the Vue plugin is the standard path today.
Run the create command with the TypeScript template on Node.js 26 LTS:
npm create vue@latest my-app
cd my-app
npm install
npm run dev During prompts, enable TypeScript, Vue Router, and Pinia if your app needs routing or shared state. The generated tsconfig.app.json, tsconfig.node.json, and env.d.ts files give you a sane baseline. Read TypeScript config explained for beginners if split configs feel opaque at first.
Enable strict mode immediately
Do not defer strictness. Flip these flags in tsconfig.app.json on day one:
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"verbatimModuleSyntax": true
}
} Strict mode catches nullable API fields, forgotten await calls, and loose object shapes before they reach QA. On client projects I maintain, teams that postpone strict mode rarely turn it on later. The migration cost only grows.
Install the right editor tooling
Use the Vue Official extension (Volar). Disable Vetur if it is still installed. Vetur does not understand Vue 3 script setup and will show false errors on valid code.
Add a type-check script that runs outside the dev server:
npm install -D vue-tsc typescript
npm pkg set scripts.type-check="vue-tsc --build --force" The dev server transpiles quickly. It does not guarantee type safety. Treat vue-tsc as the gate that blocks merges.
What are the best typing patterns for Vue 3 Composition API components?
The Composition API is where TypeScript pays off most. Options API typing works, but script setup with typed macros is cleaner and easier to maintain. See the Vue 3 Composition API deep dive for runtime behaviour; this section focuses on types.
Type props with defineProps
Prefer the type-based macro form. It reads well and composes with interfaces:
<script setup lang="ts">
interface BookingFormProps {
tourId: number
currency?: 'NPR' | 'USD'
readonly?: boolean
}
const props = withDefaults(defineProps<BookingFormProps>(), {
currency: 'NPR',
readonly: false,
})
</script> Extract shared prop interfaces into src/types/components.ts when multiple components reuse the same shape. Do not duplicate inline object types across ten files.
Type emits with defineEmits
Typed emits document the contract between child and parent. They also catch typos in event names at compile time:
<script setup lang="ts">
const emit = defineEmits<{
submit: [payload: { tourId: number; guests: number }]
cancel: []
}>()
function onSubmit() {
emit('submit', { tourId: props.tourId, guests: 2 })
}
</script> Type template refs and composables
Template refs need explicit element or component types. Without them, ref.value becomes any and strict mode loses meaning:
import { ref, onMounted } from 'vue'
const inputEl = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputEl.value?.focus()
}) Composable functions should declare return types when inference is ambiguous. Export reusable composables from src/composables/ with explicit interfaces for complex return objects. That pattern scales well on booking dashboards and admin panels I have shipped with Livewire and Vue hybrids.
How should you type props, emits, and shared state in Vue 3?
Props and emits define component boundaries. Pinia stores define application boundaries. Treat all three as published APIs. Consumers should not guess field names or payload shapes.
Pinia with TypeScript
Pinia is the default store for Vue 3. The setup-store style maps cleanly to TypeScript. See Pinia vs Vuex 4 for Vue 3 state management for migration context:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Tour } from '@/types/tour'
export const useTourStore = defineStore('tour', () => {
const tours = ref<Tour[]>([])
const loading = ref(false)
const activeCount = computed(() =>
tours.value.filter(t => t.status === 'active').length
)
async function fetchTours(): Promise<void> {
loading.value = true
try {
const res = await fetch('/api/tours')
tours.value = await res.json() as Tour[]
} finally {
loading.value = false
}
}
return { tours, loading, activeCount, fetchTours }
}) Define API response types in src/types/ and import them into stores and components. When the Laravel backend changes a field, TypeScript surfaces every front-end reference. That feedback loop saves hours during Laravel API upgrades.
Vue Router 4 typed routes
Route params and meta fields benefit from a central route record type. Untyped params invite NaN bugs when you call Number(route.params.id) on undefined input:
import type { RouteRecordRaw } from 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
title?: string
}
}
const routes: RouteRecordRaw[] = [
{
path: '/tours/:id',
name: 'tour-show',
component: () => import('@/views/TourShow.vue'),
meta: { requiresAuth: true, title: 'Tour Details' },
},
] For advanced navigation guards and lazy routes, read Vue Router 4 advanced patterns.
Which TypeScript tooling catches Vue 3 errors before production deploy?
Local dev comfort is not production safety. You need automated checks in CI that fail on type errors, unused suppressions, and broken imports. I wire the same pattern into build pipeline automation for sister sites on shared GitLab runners.
The minimum CI script set
npm run type-check— runsvue-tsc --buildacross the project.npm run build— confirms Vite production output succeeds.npm run test:unit— optional but valuable for composable logic.
A typical GitLab CI job block:
typecheck:
image: node:26
script:
- npm ci
- npm run type-check
- npm run build Run type-check before build. A green Vite build can still ship broken types if you skip vue-tsc.
Options API vs script setup: a practical comparison
Both work with TypeScript. For greenfield Vue 3 code, script setup wins on brevity and inference. Options API remains valid for legacy files you migrate incrementally.
| Criteria | Script setup + TS | Options API + TS |
|---|---|---|
| Type inference | Strong with defineProps macros | Requires propType generics or external wrappers |
| Boilerplate | Low — no return object | Higher — data, methods, computed blocks |
| Composable reuse | Native — import and call | Mixin-based patterns age poorly |
| Migration cost | Default for new files | Keep for stable legacy components |
| Editor support | Best with Volar | Good but noisier on large components |
My rule on production apps: new features use script setup. Touch a legacy Options component only when the business change requires it. Then add types while you are there.
Validate API payloads at the boundary
TypeScript erases at runtime. A typed interface does not prove the JSON from your server matches. Parse responses with a runtime validator at the network boundary. Zod is a common choice. Keep validators next to API client modules:
import { z } from 'zod'
export const TourSchema = z.object({
id: z.number(),
title: z.string(),
priceNpr: z.number(),
})
export type Tour = z.infer<typeof TourSchema> When debugging malformed JSON during integration, paste samples into the JSON formatter tool to inspect structure before you update schemas.
How do you integrate a typed Vue 3 front end with a Laravel backend?
Most of my Vue work sits inside Laravel apps, not standalone SPAs. Adventure Third Pole Trek and similar booking platforms use Laravel for auth, payments, and PDF workflows. Vue handles interactive dashboards. The integration point is the API contract.
Share types without sharing repos blindly
You rarely want Laravel PHP types auto-generated on every front-end save unless you already run a monorepo toolchain. Practical options ranked by effort:
- OpenAPI spec — Generate TypeScript clients from a documented REST API. Best for public or partner APIs.
- Hand-maintained
src/types/api.ts— Fine for small teams with stable endpoints. - JSON Schema from Form Requests — Useful when validation rules are the source of truth.
Align field naming in PHP resources and TypeScript interfaces. Mixed camelCase and snake_case causes silent mapping bugs. Pick one convention at the JSON boundary and transform in one place.
Axios or fetch wrappers with typed errors
import axios from 'axios'
import type { AxiosError } from 'axios'
import type { ValidationErrorResponse } from '@/types/api'
export const api = axios.create({
baseURL: '/api',
headers: { Accept: 'application/json' },
})
export function isValidationError(
err: unknown
): err is AxiosError<ValidationErrorResponse> {
return axios.isAxiosError(err) && err.response?.status === 422
} Centralize interceptors for CSRF tokens and 401 redirects. Laravel Sanctum cookie auth needs consistent withCredentials settings. Document that in your project README so the next developer does not break session auth.
Build output and deployment
Commit built assets or build in CI depending on your server constraints. Several sites I deploy have no Node on production. We build on the runner with npm 12, commit public/build, and ship via Deployer symlink releases. After deploy, reload PHP-FPM so opcache picks up changed PHP files even when only the manifest changed.
For larger SPAs that need SSR, evaluate Vue 3 SSR with Nuxt 3 separately. Most Laravel dashboards do not need SSR on day one.
What common TypeScript with Vue 3 mistakes break production builds?
These failures recur across teams new to typed Vue. Each one is cheap to prevent and expensive to debug under deadline pressure.
Using any to silence errors
any defeats the purpose of strict mode. Replace it with unknown and narrow, or define a proper interface. If third-party libraries ship poor types, wrap them in a small typed adapter module. Do not spread any into your domain code.
Ignoring .vue module declarations
Ensure env.d.ts includes the Vue module shim:
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
} Without this file, imports like import App from './App.vue' fail type-check even when the app runs fine in dev.
Skipping generic constraints on composables
export function useList<T extends { id: number }>(items: Ref<T[]>) {
const byId = (id: number) => items.value.find(i => i.id === id)
return { byId }
} Generics keep composables reusable without casting. They pay off when the same list helper serves tours, lawyers, and products on directory sites like Lawyers Pokhara.
Forgetting SEO and SPA routing
Typed Vue SPAs still need crawlable URLs and metadata. Technical SEO for interactive apps is a separate concern. Read SEO for single-page applications and wire meta tags through Vue Router guards or a head manager.
Consult the official Vue.js TypeScript guide for macro syntax updates. Cross-check compiler behaviour against the TypeScript handbook when strict flags behave unexpectedly. For Vite-specific module resolution, the Vite documentation covers vite/client types and path aliases.
If you are migrating from plain JavaScript, work file by file. Enable allowJs temporarily, type new modules first, and tighten legacy code when you touch it. That incremental path matches how I upgrade long-lived portals without a risky big-bang rewrite. Pair the effort with TypeScript for JavaScript developers for mental model shifts around structural typing.
Quality assurance belongs in the release process, not only in the editor. Add typed component tests for critical composables and run them in CI alongside vue-tsc. For broader QA strategy on production apps, see testing and optimization services and Laravel feature testing best practices for the PHP side of the stack.
On eCommerce and booking UIs, typed filters and cart state reduce checkout bugs. Product filter UX patterns from eCommerce product filters UX best practices apply directly to typed Pinia stores backing faceted search. The Adventure Third Pole Trek booking platform is a real example of Laravel plus interactive front-end modules working together.
When scoping a new typed Vue module for your business, custom software development and web development services cover everything from component libraries to full deploy pipelines. For greenfield SPAs with heavy interactivity, enterprise application development may fit better than ad-hoc page scripts.
Keep path aliases consistent between vite.config.ts and tsconfig.app.json. A common failure mode is @/components/Foo.vue resolving in Vite but failing in vue-tsc because only one config defines the alias. Mirror the resolve.alias block in both files.
Prefer explicit return types on public composables exported from packages shared across apps. Internal components can rely on inference. The distinction keeps library boundaries clear when multiple teams import the same code.
Document npm engine requirements in package.json so CI and local machines stay on Node.js 26 LTS. Type-check results can differ across major Node versions when native dependencies are involved.
Key Takeaways
- Scaffold with the official Vue + TypeScript template, enable strict mode immediately, and use Volar instead of Vetur.
- Type props, emits, refs, Pinia stores, and API responses as published contracts—not optional annotations.
- Run
vue-tsc --buildin CI beforevite build; a green dev server does not prove type safety. - Validate JSON at the network boundary with Zod or similar; TypeScript types disappear at runtime.
- Align Laravel API field naming with front-end interfaces and centralize HTTP error handling.
- Fix root causes when type-check fails—never paper over errors with
anyor@ts-ignore.
People Also Ask
Is TypeScript worth it for Vue 3 in 2026?
Yes, for any Vue 3 app expected to live beyond a single release cycle. Types catch prop drift, refactor breakage, and API mismatches early. The upfront cost is one to two days of strict setup. Ongoing savings show up every time you rename a field or upgrade a dependency.
Should I use script setup or Options API with TypeScript?
Use script setup for all new components. It offers the cleanest typing with defineProps and defineEmits macros. Keep Options API files as-is until a business change requires editing them. Then add types during that edit rather than scheduling a standalone migration.
What is the difference between vue-tsc and tsc?
Standard tsc does not understand .vue single-file components. vue-tsc wraps the TypeScript compiler with Vue-aware processing so template and script blocks type-check together. Always use vue-tsc for Vue projects.
Can I use TypeScript Vue 3 with Laravel without Inertia?
Yes. Many Laravel apps mount Vue on specific Blade pages or serve a small SPA under /app. Sanctum handles auth. Vite compiles assets into public/build. You do not need Inertia unless you want server-driven routing with Vue page components.
Ship typed Vue 3 code you can maintain next year
TypeScript with Vue 3 best practices are not about chasing clever types. They are about making refactors safe, onboarding faster, and production bugs rarer. Start strict, type your boundaries, run vue-tsc in CI, and treat API contracts as shared truth between Laravel and Vue. That stack has survived real client deadlines on booking portals, legal-tech dashboards, and eCommerce admin panels. Need help wiring typed Vue modules into an existing Laravel app or greenfield build? Contact us to discuss architecture, CI setup, and deployment on your timeline.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

