You are starting a new Vue.js 3 project and face a crossroads: use the classic Options API or adopt the Composition API? You are not alone. Every week we discuss with clients and development teams which path to take. The right answer is not "always this" or "never that": it depends on context, complexity, and team experience. At Meteora Web, we have built dozens of Vue applications — from small widgets to complex Laravel + Vue platforms — and we have felt the pros and cons of both APIs firsthand. This guide will help you choose wisely, without fanaticism.
Understanding the Fundamental Differences
Before deciding, you need to understand what really changes between the two APIs. It's not just syntax: it's a shift in how you organize logic.
Options API: The Classic That Works
If you've used Vue 2, you know the Options API. You define a component as an object with specific properties: data, methods, computed, watch, mounted, and so on. Each option has its fixed place.
export default {
data() {
return {
count: 0,
step: 1
}
},
computed: {
double() {
return this.count * 2
}
},
methods: {
increment() {
this.count += this.step
}
}
}
It is simple, predictable, and anyone who has seen a Vue component understands it immediately. The problem emerges when the component grows. Different pieces of logic (e.g., authentication, form validation, API calls) end up mixed in mounted and methods without clear separation. We have seen this in legacy projects: an 800-line component with data containing UI flags, user state, and notifications. A mess.
Composition API: Organizing by Feature
The Composition API introduces the setup function and the concept of composables. You are no longer constrained to fixed sections: you can group logic by feature, extract reusable functions, and have a flow more similar to plain JavaScript.
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const step = 1
const double = computed(() => count.value * 2)
function increment() {
count.value += step
}
return { count, double, increment }
}
}
Notice: count is a ref object because Vue needs to track reactivity. You can use reactive for complex objects. The key is that we can now move these functions into an external file, creating a composable.
// useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialStep = 1) {
const count = ref(0)
const step = initialStep
const double = computed(() => count.value * 2)
function increment() {
count.value += step
}
return { count, double, increment }
}
And in the component:
import { useCounter } from './useCounter'
export default {
setup() {
const { count, double, increment } = useCounter(2)
return { count, double, increment }
}
}
Now the component is slim and the logic is testable and reusable. That’s the real superpower of the Composition API.
When to Choose the Options API
We don't demonize the Options API. In many scenarios it is still the best choice:
- Simple and small components (e.g., a button, an input, a badge). If you have few lines of logic, the Options API is more straightforward and less verbose.
- Teams with little Vue experience or coming from Vue 2. The learning curve is lower and the code is more self-explanatory.
- Small or one-off projects (e.g., a landing page with a couple of interactive components). The overhead of the Composition API is not justified.
- When using third-party libraries that expect Options API (rare but possible).
We ourselves, in many internal projects or with clients who require immediate maintainability, use the Options API for 70% of components. We reserve the Composition API for complex logic or components that need to be reused across contexts.
When to Switch to the Composition API
The Composition API shines in these cases:
- Components with mixed logic (e.g., a form that handles validation, file upload, autocomplete). With the Options API you would end up with a tangle; with the Composition API each feature can be a separate composable.
- Reusing logic across components. Instead of mixins (problematic) or slots, you use composables. For example, a
useAuthcomposable to manage login state throughout the app. - Unit testing. Composables are plain JavaScript functions, testable without mounting components. We reduced test time by 40% after switching to composables.
- Large applications with dozens of components. The Composition API allows you to organize code by domain, not by option type.
- TypeScript. The Composition API is easier to type (automatic inference with
refandreactive).
A concrete example: a client needed a dashboard component with filters, charts, and a table. With the Options API we would have had a 500-line monster. With the Composition API we created useFilters, useChartData, and useTableState. Each composable was tested in isolation and reused in other views. Done.
How to Migrate Gradually (Without Rewriting Everything)
You don't need to convert every component. Vue 3 supports both APIs in the same project. You can start using the Composition API for new complex components and leave old ones in Options API. Over time, when you touch a component for a bug or feature, you can refactor it.
Here is an example of migrating a component with Options API that uses mounted to fetch data:
// Options API
import axios from 'axios'
export default {
data() {
return { users: [], loading: false }
},
mounted() {
this.loadUsers()
},
methods: {
async loadUsers() {
this.loading = true
try {
const res = await axios.get('/api/users')
this.users = res.data
} finally {
this.loading = false
}
}
}
}
Version with Composition API:
import { ref, onMounted } from 'vue'
import axios from 'axios'
export default {
setup() {
const users = ref([])
const loading = ref(false)
async function loadUsers() {
loading.value = true
try {
const res = await axios.get('/api/users')
users.value = res.data
} finally {
loading.value = false
}
}
onMounted(loadUsers)
return { users, loading }
}
}
You can also extract the logic into a useUsers composable immediately or later.
Common Mistakes and Best Practices
We have seen some frequent mistakes when moving to the Composition API:
- Losing reactivity: using
const count = 0instead ofref(0). Remember: Vue does not track plain variables. - Mixing styles in the same file: avoid having half the component in Options and half in setup. Choose one and be consistent.
- Creating too many refs for each property: sometimes a
reactiveobject is cleaner. Example:const state = reactive({ count: 0, step: 1 }). - Not handling cleanup: in composables that use
watchoronMounted, remember to stop effects withonUnmountedorwatchEffectwith cleanup. - Over-engineering: don't create a composable for every single line of logic. If a component has 3 lines of setup, stick with Options API or simple refs.
A best practice we follow: always start with Options API, then extract into composables only when logic grows or repeats. This avoids premature abstraction.
In Summary — What to Do Now
- Evaluate your context: simple component? Junior team? Small project? Use Options API.
- Identify reusable logic: authentication, filters, common API calls? Write a composable with Composition API.
- Don't rewrite everything: mix both APIs in the same project until refactoring is necessary.
- Test composables in isolation: they are pure functions, take advantage of that.
- Document decisions in the team: establish a convention (e.g., “new components with logic > 50 lines use Composition API”).
We at Meteora Web use this rule of thumb: Options API works great for presentation components, Composition API for business logic. If in doubt, start with Options API and refactor when you smell code duplication. Just as we saw with React Hooks, the ability to extract logic into standalone functions changes a project's maintainability. Choose with your head, not with the hype.
Sponsored Protocol