Highlights
Migrating from Vue’s Options API to Composition API isn’t just a refactor; it’s a mindset shift. No more this, no more logic scattered across five different option blocks, no more pretending that 400-line components are fine. The Composition API forces you to write code that’s modular, explicit, and actually reusable. This blog walks through a real migration: the strategy, the composables, the performance gains, the challenges that don’t show up in the documentation, and the lessons learned the hard way. It also includes a practical checklist and an honest answer to the question every Vue developer eventually asks: Is this migration worth it?
If you’re working with Vue 3, you’ve probably heard about the Composition API. Maybe you’ve even tried it on a small project. But migrating a large, production application? That’s a different story altogether.
I recently migrated to a complex enterprise dashboard from Options API to Composition API. The application handles dynamic query building, data management, real-time visualization, and complex form validations. With components exceeding 3,600 lines of code and intricate state management, this wasn’t a simple refactor. It required careful planning, incremental changes, and a lot of learning along the way.
In this blog, you’ll learn:
- Why I decided to migrate to Composition API
- My step-by-step migration strategy
- Real code examples (before and after)
- Challenges I faced and how I solved them
- Key lessons for your own migration journey
Without wasting any time, let’s jump right into it.
Why are developers switching from Options API to Composition API
Before jumping into the “how,” let’s talk about the “why?” Why was my decision to migrate not made lightly?
Here’s why I migrated to Composition API (and maybe so should you):
- Better Code Organization with the Options API: Related logic was scattered across data, computed, methods, and watch. In large components, finding all the code related to a single feature meant jumping between different sections. The Composition API allows grouping related logic together.
- Improved Reusability: I duplicated logic across components, fetching data, managing form state, and handling API calls. With the Composition API, I could extract this logic into composables and reuse them across the application.
- Better IDE Support: The Composition API provides superior autocomplete and type of inference, making development faster and reducing bugs.
- Futureproofing: Vue 3 ecosystem is moving towards Composition API. New libraries, tools, and best practices are built with Composition API in mind.
To really appreciate why the migration was worth it, it helps to understand exactly what changed and, more importantly, why it matters how you write going forward.
What is the difference between Composition API and Options API?
Before diving into migration examples, let’s compare the two approaches:
Options API
export default {
data() {
return { user: null, loading: false }
},
computed: {
fullName() {
return `${this.user.firstName} ${this.user.lastName}`
}
},
methods: {
async fetchUser() {
this.loading = true
const response = await api.getUser()
this.user = response.data
this.loading = false
}
},
mounted() {
this.fetchUser()
}
}
Composition API
import { ref, computed, onMounted } from 'vue'
const user = ref(null)
const loading = ref(false)
const fullName = computed(() =>
`${user.value.firstName} ${user.value.lastName}`
)
async function fetchUser() {
loading.value = true
const response = await api.getUser()
user.value = response.data
loading.value = false
}
onMounted(() => fetchUser())
One of the first things you notice when you make the switch is just how different the mental model feels.
Here are the key differences between Composition and Options API:
- No more this keyword
In Options API, this was your gateway to everything: data, methods, computed properties, and lifecycle hooks. In Composition API, that’s gone entirely. Instead, you work with variables and functions directly in scope, which feels unfamiliar at first but makes your code significantly easier to reason with, especially in larger components. - Explicit imports for Vue features
Nothing comes for free anymore. Need a reactive variable? Import ref. Need a computed property? Import computed. This might feel verbose initially, but it’s actually a feature — your dependencies are visible, intentional, and tree-shakeable, which means your bundle only ships what you actually use. - All logic in one place
In Options API, related logic was often split across data, methods, computed, and watch, sometimes pages apart. Composition API lets you group everything by feature, not by option type. If you’ve ever scrolled up and down a 400-line component trying to connect the dots, you’ll appreciate this immediately. - Must use .value to access or modify refs.
When you declare a reactive variable with ref(), you can’t just read or update it directly; you must go through .value. It’s a small syntactic overhead that exists for a good reason: it’s how Vue tracks reactivity under the hood, but it’s worth knowing upfront, so it doesn’t catch you off guard in production.
Once that distinction clicked for me, the next question was straightforward: how do you actually go about making the switch without breaking everything in the process?
How do you plan a migration from Options API to Composition API?
I didn’t migrate everything at once, and honestly, that decision saved me a lot of headaches. The good news is that Vue 3 fully supports both APIs simultaneously, which means you don’t have to choose one or the other overnight. You can migrate incrementally, component by component, at a pace that doesn’t put your production at risk.
Here’s the approach I took for migrating Options API to Composition API:
Phase 1: Small Components First
- Migrated utility components (buttons, inputs, dialogs)
- Low risk, high learning value
- Built confidence
Phase 2: Medium Complexity Components
- Migrated from components and data tables
- Started extracting composables
- Refined patterns
Phase 3: Large, Complex Components
- Migrated biggest components (3000+ lines)
- Heavy refactoring and composable extraction
- Most challenging but most rewarding
Phase 4: Cleanup and Optimization
- Removed unused code
- Optimized composables
- Updated documentation
Total Migration Time: ~4 months
Strategy on paper is one thing, but the real learning happens when you apply it to actual code. Here’s what that looked like in my codebase.
What does a real Options API to Composition API migration look like?
Let me show you a complex component migration from my codebase.
Before (Options API):
export default {
name: 'DataBuilder',
props: { category: String, type: String },
data() {
return {
query: null,
itemsList: [],
propertiesList: [],
itemsLoading: false,
propertiesLoading: false
}
},
computed: {
isLoading() {
return this.itemsLoading || this.propertiesLoading
}
},
methods: {
async fetchItems() {
this.itemsLoading = true
const response = await api.getItems(this.category, this.type)
this.itemsList = response.data
this.itemsLoading = false
},
async fetchProperties() {
this.propertiesLoading = true
const response = await api.getProperties(this.category, this.type)
this.propertiesList = response.data
this.propertiesLoading = false
}
},
watch: {
category() {
this.fetchItems()
this.fetchProperties()
}
},
mounted() {
this.fetchItems()
this.fetchProperties()
}
}
After (Composition API with Composables):
import { ref, computed, watch, onMounted } from 'vue'
import { useItemData } from '@/composables/useItemData'
const props = defineProps({
category: String,
type: String
})
// Using composable for reusable logic
const {
itemsList,
propertiesList,
loading,
fetchItems,
fetchProperties
} = useItemData(props.category, props.type)
// Watchers
watch([() => props.category, () => props.type], () => {
fetchItems()
fetchProperties()
})
// Lifecycle
onMounted(() => {
fetchItems()
fetchProperties()
})
The Composable (useItemData.js):
CopyCreateApply Diffjavascript
import { ref } from 'vue'
export function useItemData(category, type) {
const itemsList = ref([])
const propertiesList = ref([])
const loading = ref(false)
async function fetchItems() {
loading.value = true
const response = await api.getItems(category, type)
itemsList.value = response.data
loading.value = false
}
async function fetchProperties() {
loading.value = true
const response = await api.getProperties(category, type)
propertiesList.value = response.data
loading.value = false
}
return { itemsList, propertiesList, loading, fetchItems, fetchProperties }
}
Here are the benefits of Composition API:
- Reusability: Used in 15+ components
- Consistency: Same logic everywhere
- Maintainability: Fix once, fixed everywhere
- Testability: Test the composable once
- Readability: Component code is cleaner
Of course, no real-world migration is without its rough patches, and this one was no exception. Here’s where things got complicated.
What are the most common problems when migrating to Composition API, and how do you fix them?
Here are a few challenges I faced and how I solved them.
Challenge 1: This Context
Problem: I kept trying to use this.someProperty out of habit.
Solution:
// Wrong
const count = ref(0)
function increment() {
this.count++ // Error!
}
// Correct
const count = ref(0)
function increment() {
count.value++ // Works!
}
Challenge 2: Reactivity Gotchas
Problem: Understanding ref() vs reactive() and .value unwrapping.
My Guidelines:
- Use ref() for primitives and arrays
- Use reactive() for objects
- When in doubt, use ref()
// I use ref for arrays
const itemsList = ref([])
// I use reactive for complex state
const state = reactive({
categoryOptions: [],
typeOptions: []
})
// I use ref for simple values
const loading = ref(false)
Challenge 3: Watchers Syntax
Before:
watch: {
userId(newVal) {
this.fetchUser(newVal)
}
}
After:
watch(() => props.userId, (newVal) => {
fetchUser(newVal)
})
// Watch multiple sources
watch(
[() => props.category, () => props.type],
([newCategory, newType]) => {
fetchItems()
}
)
Working through those challenges was worth it, and the performance numbers made that very clear, very quickly.
Does migrating to Composition API improve performance?
After completing my migration, I measured these improvements:
Bundle Size: 2.8 MB → 2.3 MB (18% reduction) Component Init: ~450ms → ~320ms (29% faster) Memory Usage: ~85 MB → ~68 MB (20% reduction) HMR Speed: 40% faster
Beyond the metrics, though, the migration taught me things about my own code that no benchmark could, and those lessons have stuck with me since.
Speaking of performance wins, see how Nitor’s product engineering approach helped a startup slash a 15-day process down to 5 seconds.
Download Case Study
What do developers wish they knew before migrating to Composition API?
Looking back, a few decisions made the entire migration smoother, and I’d make every one of them again without hesitation.
What Worked Well:
1. Incremental migration gave me time to learn
Not migrating everything at once meant I could absorb the Composition API’s patterns gradually, course-correct early mistakes before they spread across the codebase and keep the app stable throughout the entire process.
2. Extracting composables early made everything cleaner
The moment I started pulling shared logic into composables, the benefits of Composition API became undeniable. The code that used to live in five different places now had a single, reusable home, and every component that consumed it got simpler as a result.
3. Code reviews spread knowledge across the team
Using every pull request as a teaching opportunity meant the whole team leveled up together, rather than one person becoming the Composition API expert everyone else depended on.
4. Documentation helped onboard new developers
Writing down our patterns, conventions, and the reasoning behind key decisions as we went meant new team members could get productive quickly without needing a lengthy handholding session from someone who’d lived through the migration firsthand.
If I were to distill everything into something actionable, it would look something like this: a checklist I wish I’d had on day one.
What is the step-by-step checklist for migrating from Options API to Composition API?
Here are a few items to check before migration:
- Read and understand the component
- Write tests if missing
- Identify reusable logic for composables
Here are a few items to check during migration:
- Replace export default with <script setup>
- Convert data() to ref() or reactive()
- Convert methods to regular functions
- Replace this with direct variable access
- Extract reusable logic to composables
Here are a few items to check after migration:
- Run all tests
- Manual testing in browser
- Code review
- Check bundle size impact
That said, a checklist is only useful if the timing is right, so before you dive in, it’s worth asking whether this migration actually makes sense for where your project is right now.
Should I migrate my project from Options API to Composition API?
Migrate If:
- Starting a new Vue 3 project
- Have complex components with scattered logic
- Have duplicated logic across components
- Want better IDE support
Wait If:
- Still on Vue 2 (migrate to Vue 3 first)
- Have tight deadlines
- Components are simple and well-organized
And with that, we’ve covered the full arc, from the ‘why?’ to the ‘how?’ to the ‘whether?’ Let’s bring it home.
Is migrating from Options API to Composition API worth it?
Migrating from Options API to Composition API was one of the best decisions I made for my Vue 3 application. While it took 4 months, the benefits were worth it:
- Better code organization
- Improved reusability through composables
- Enhanced developer experience
- Performance gains
- Future-ready codebase
Key Takeaways:
- Start small and migrate incrementally
- Extract composables early and often
- Use code reviews to spread knowledge
- Don’t rush quality over speed
- Write tests before migrating
The Composition API isn’t just a different way to write Vue components; it’s a better way to organize, reuse, and maintain your code. Code quality, architectural decisions, and engineering discipline compound over time, and that’s exactly what Nitor Infotech’s Product Engineering services are built around. Contact us today!