Options API forces you to split related logic:
// Options API
export default {
data() {
return { name: '', email: '' }
},
computed: {
isValid() { return this.name && this.email.includes('@') }
},
methods: {
submit() {
if (this.isValid) alert(`Submitting: ${this.name}`)
}
}
}
<script setup>
const name = ref('')
const email = ref('')
const isValid = computed(() => name.value && email.value.includes('@'))
function submit() {
if (isValid.value) alert(`Submitting: ${name.value}`)
}
</script> // useFormValidation.ts
export function useFormValidation() {
const name = ref('')
const isValid = computed(() => name.value.length > 2)
return { name, isValid }
}
Now you can reuse logic across components, test in isolation, and eliminate conflicts.
// useCounter.ts
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// useCounter.spec.ts
const { count, increment } = useCounter()
expect(count.value).toBe(0)
increment()
expect(count.value).toBe(1)
When your app grows, Composition API makes it easy to:
“After migrating our enterprise app, feature velocity increased and onboarding time dropped.”
— Monterail Team
Most new Vue tools assume Composition API:
While Options API still works, many libraries assume you're using Composition API, leading to extra boilerplate when integrating.
Composition API:
const count = ref<number>(0) import { defineComponent, PropType } from 'vue'
defineComponent({
props: {
count: Number as PropType<number>
}
}) Composition API gives you power — but with great freedom comes potential chaos:
✅ Use ESLint rules, layered structure, and naming conventions to stay sane.
Tip: Use ref for primitives and reactive for objects. Avoid destructuring unless using toRefs() or storeToRefs().
Bonus:
const state = reactive({ count: 0 })
const { count } = state // breaks reactivity
// Instead, use:
const { count } = toRefs(state)
Composition API assumes:
For junior-heavy teams or short-term projects, Options API may still be faster.
Vue 3 lets you mix both APIs. Start small:
Bonus: you can train the team gradually — no rewrite required.
In Vue 3, composables replaced mixins — and they only work inside setup().
If your codebase still uses the Options API, using VueUse, Vue Router, Pinia, or even Nuxt utilities becomes awkward. You’ll end up duplicating state, breaking reactivity, or writing glue code just to access core features.
<template>
<div>
<p>Current route: {{ currentRoute.path }}</p>
<p>Window is {{ isLargeScreen ? 'large' : 'small' }}</p>
</div>
</template>
<script>
import { useRoute } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
export default {
setup() {
const route = useRoute()
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
return {
currentRoute: route,
isLargeScreen
}
},
data() {
return {
foo: 'bar'
}
},
created() {
// Want to access `currentRoute` or `isLargeScreen` here?
// Nope — it's not on `this`, it's in the setup return.
console.log('Cannot access composables from here')
}
}
</script>
Keep Options API if:
Composition API is not a must. It’s a tool — use it when it solves problems.
Refactoring from Options to Composition API can bring:
But you don’t need to do it all at once.
Recommended approach:
Vue gives you the flexibility to evolve at your own pace.
Refactor one component. Create one composable.
You’ll see why more teams are choosing Composition API in 2025 and beyond.
npm create vue@latest
# Choose: "Composition API + <script setup>"