From React to Vue: My Framework Migration Journey and Why I'm Never Looking Back
A developer's honest experience migrating from React to Vue.js, comparing Next.js vs Nuxt.js, and discovering why Vue's developer experience with Nuxt modules and Pinia stores changed everything. Real insights from building production applications.

When Your Job Forces a Framework Change (And You Discover Something Better)
I'll be honest—I didn't choose Vue.js initially. It chose me.
When I moved from Vahak to Acalvio, the tech stack changed from React to Vue. My first reaction? "Great, now I have to learn a new framework." I'd invested years in React, mastered its ecosystem, built projects with Next.js. Starting over felt frustrating.
Then I wrote my first Vue component. Then I configured my first Nuxt project. Then I used Pinia for state management.
Three months later, I rebuilt my personal website from React to Vue. Not because I had to—because I wanted to.
In this post, I'll share my honest experience migrating from React to Vue, why Vue's developer experience surprised me, and what I learned comparing Next.js to Nuxt.js in production.
The Forced Migration: From React at Vahak to Vue at Acalvio
My React Background
At Vahak, I was deep in the React ecosystem:
- React + TypeScript for frontend development
- Next.js for server-side rendering
- Redux Toolkit for state management
- React Query for server state
- Styled Components for styling
I was comfortable. The React patterns were second nature. I could scaffold a new React project in minutes.
The Vue Requirement
Starting at Acalvio meant:
- Vue 3 + Composition API (completely new syntax)
- Nuxt 3 (learning a different meta-framework)
- Pinia (new state management approach)
- Different ecosystem (unfamiliar libraries and tools)
My initial concerns:
- "Will I be as productive in Vue?"
- "Is Vue's ecosystem as mature as React's?"
- "Will I have to relearn everything?"
- "What about all my React knowledge?"
First Impressions: Vue Felt... Different
The Learning Curve
Week 1: Struggled with Single File Components (SFCs)
<!-- This felt weird coming from JSX -->
<template>
<div class="container">
<h1>{{ title }}</h1>
<button @click="increment">Count: {{ count }}</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const title = ref('Hello Vue');
const count = ref(0);
function increment() {
count.value++;
}
</script>
<style scoped>
.container {
padding: 2rem;
}
</style>
My React brain: "Where's the JSX? Why is count.value needed? Why separate template, script, and style?"
Week 2: Started appreciating the structure
The separation of concerns made sense. Templates for markup, script for logic, styles scoped to components. No more mixing everything in JSX.
Week 3: Realized I was more productive
Writing components became faster. The patterns were clear. The tooling was excellent.
Month 2: Built features faster than I did in React
The developer experience improvements accumulated. I was shipping code with fewer bugs.
What Surprised Me About Vue
1. Reactivity Just Works
In React:
// React - useState for everything
const [user, setUser] = useState({ name: '', age: 0 });
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Need to spread objects to trigger re-renders
setUser({ ...user, name: 'John' });
In Vue:
// Vue - ref() and reactive() for reactivity
const user = ref({ name: '', age: 0 });
const loading = ref(false);
const error = ref(null);
// Just mutate, Vue tracks changes
user.value.name = 'John';
The difference: No useState management. No worrying about immutability. Just change values and Vue handles the rest.
2. Computed Values Are Automatic
In React:
// React - useMemo to avoid recalculations
const fullName = useMemo(() => {
return `${firstName} ${lastName}`;
}, [firstName, lastName]);
const discountedPrice = useMemo(() => {
return price * (1 - discount);
}, [price, discount]);
In Vue:
// Vue - computed() caches automatically
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const discountedPrice = computed(() => price.value * (1 - discount.value));
The difference: Computed properties cache automatically. No dependency arrays. Less boilerplate.
3. Two-Way Binding Simplified Forms
In React:
// React - controlled components need onChange handlers
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
In Vue:
<!-- Vue - v-model handles two-way binding -->
<script setup>
const email = ref('');
const password = ref('');
</script>
<template>
<input v-model="email" />
<input v-model="password" />
</template>
The difference: v-model eliminates boilerplate. Forms are simpler to build.
Nuxt vs Next.js: The Meta-Framework Showdown
When I rebuilt my personal website, I compared Nuxt and Next.js directly.
Configuration: Nuxt Wins by a Mile
Next.js configuration:
// next.config.js - fragmented configuration
module.exports = {
images: { /* image config */ },
experimental: { /* experimental features */ },
// Separate files for:
// - _app.js (layout)
// - _document.js (HTML structure)
// - middleware.ts (middleware)
};
Nuxt configuration:
// nuxt.config.ts - everything in one place
export default defineNuxtConfig({
modules: [
'@nuxt/ui', // UI components - one line!
'@nuxt/content', // CMS - one line!
'@nuxt/image', // Image optimization - one line!
'@pinia/nuxt', // State management - one line!
'@nuxtjs/seo', // SEO meta tags - one line!
],
ssr: true,
routeRules: {
'/': { prerender: true },
'/blog/**': { prerender: true },
},
image: {
quality: 80,
formats: ['webp'],
},
});
Real example from my project:
My nuxt.config.ts is 168 lines and includes:
- SSR configuration
- Prerendering rules
- Module setup (7 modules!)
- Image optimization
- SEO configuration
- Font loading
- Performance optimizations
In Next.js, this would be spread across 5+ files.
Modules: The Killer Feature
This is where Nuxt destroyed Next.js for me.
Want a blog?
# Nuxt
pnpm add @nuxt/content
# nuxt.config.ts
modules: ['@nuxt/content']
Done. You now have:
- Markdown support
- MDC (Markdown Components)
- Syntax highlighting
- Type-safe content queries
- Built-in search
- Auto-generated routes
In Next.js?
Install 5+ packages:
gray-matterfor frontmatterremarkand plugins for markdownrehypeand plugins for HTML- Custom API routes
- Manual route generation
- Type definitions yourself
Want image optimization?
# Nuxt
modules: ['@nuxt/image']
You get:
- Automatic WebP conversion
- Responsive images
- Lazy loading
- CDN integration
- Image optimization out of the box
In Next.js?
next/image works, but configuration is more complex and less flexible.
Real Project Comparison
I built the same website in both frameworks:
Next.js project:
- 15+ dependencies for basic functionality
- Multiple configuration files
- Manual setup for common features
- More boilerplate code
Nuxt project:
- 7 core modules handle everything
- Single configuration file
- Features work out of the box
- Less code to maintain
The kicker: My Nuxt site is faster, has better SEO, and was quicker to build.
Pinia vs Redux: State Management Revolution
Redux in React
// Redux - so much boilerplate
import { createSlice, configureStore } from '@reduxjs/toolkit';
const alertSlice = createSlice({
name: 'alerts',
initialState: { alerts: [] },
reducers: {
addAlert: (state, action) => {
state.alerts.push(action.payload);
},
removeAlert: (state, action) => {
state.alerts = state.alerts.filter(a => a.id !== action.payload);
},
},
});
export const { addAlert, removeAlert } = alertSlice.actions;
export default alertSlice.reducer;
// Need provider in _app.js
import { Provider } from 'react-redux';
To use:
import { useDispatch, useSelector } from 'react-redux';
const dispatch = useDispatch();
const alerts = useSelector(state => state.alerts.alerts);
dispatch(addAlert({ id: 1, message: 'Hello' }));
Pinia in Vue
// stores/AlertStore.ts - from my actual project
import { defineStore } from 'pinia';
export const useAlertStore = defineStore('alert', () => {
const alerts = ref<Alert[]>([]);
function addAlert(message: string, type: AlertType) {
const id = Date.now();
alerts.value.push({ id, message, type });
setTimeout(() => removeAlert(id), 5000);
}
function removeAlert(id: number) {
alerts.value = alerts.value.filter(a => a.id !== id);
}
return { alerts, addAlert, removeAlert };
});
To use:
// In any component - no provider needed!
const alertStore = useAlertStore();
alertStore.addAlert('Success!', 'success');
The difference:
- Redux: 30+ lines, provider setup, useDispatch, useSelector, actions, reducers
- Pinia: 15 lines, auto-imports, direct usage, composition API style
Pinia felt like state management should work.
Real Code from My Migration
Before: React Component
// React - lots of useState and useEffect
import { useState, useEffect, useMemo } from 'react';
export default function BlogList() {
const [blogs, setBlogs] = useState([]);
const [loading, setLoading] = useState(true);
const [category, setCategory] = useState('all');
useEffect(() => {
async function fetchBlogs() {
setLoading(true);
const res = await fetch(`/api/blogs?category=${category}`);
const data = await res.json();
setBlogs(data);
setLoading(false);
}
fetchBlogs();
}, [category]);
const filteredBlogs = useMemo(() => {
return blogs.filter(b =>
category === 'all' || b.category === category
);
}, [blogs, category]);
if (loading) return <div>Loading...</div>;
return (
<div>
{filteredBlogs.map(blog => (
<BlogCard key={blog.id} blog={blog} />
))}
</div>
);
}
After: Vue Component
<!-- Vue - cleaner and more intuitive -->
<script setup lang="ts">
const category = ref('all');
const { data: blogs, pending: loading } = await useAsyncData(
`blogs-${category.value}`,
() => queryCollection('blogs')
.where('primary_category', '==', category.value)
.all()
);
const filteredBlogs = computed(() =>
blogs.value?.filter(b =>
category.value === 'all' || b.primary_category === category.value
) ?? []
);
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else>
<BlogCard
v-for="blog in filteredBlogs"
:key="blog.stem"
:blog="blog"
/>
</div>
</template>
Benefits:
- No manual loading state management
useAsyncDatahandles SSR automatically- Computed values cached automatically
- Template syntax clearer than JSX
- Less code overall
Developer Experience: The Real Game Changer
What Made Vue Better for Me
1. Auto-Imports
In Nuxt, I don't import basic utilities:
// Nuxt - just use it
const route = useRoute();
const router = useRouter();
const config = useRuntimeConfig();
// No imports needed! Nuxt provides them.
In Next.js:
// Next.js - import everything
import { useRouter } from 'next/router';
import { useEffect, useState, useMemo } from 'react';
2. File-Based Routing That Actually Works
Both have it, but Nuxt's is better:
pages/
blog/
[category]/
[page].vue → /blog/algorithms/1
index.vue → /blog/algorithms
[id].vue → /blog/some-post
index.vue → /blog
Layouts, middleware, and route rules in one place. It just makes sense.
3. Composables vs Hooks
React hooks have rules. Vue composables don't.
// Vue - composables can be called anywhere
function useCustomComposable() {
const data = ref(null);
// Can use other composables freely
const route = useRoute();
const config = useRuntimeConfig();
// Can be in loops or conditions
if (someCondition) {
const extra = useExtraData();
}
return { data };
}
React's rules of hooks frustrated me. Vue's flexibility is refreshing.
4. TypeScript Support
Both support TypeScript, but Vue 3's Composition API with TypeScript feels more natural:
// Vue - types are straightforward
interface Blog {
title: string;
content: string;
}
const blogs = ref<Blog[]>([]);
const selectedBlog = computed<Blog | null>(() =>
blogs.value.find(b => b.id === selectedId.value) ?? null
);
Building My Website: The Migration Story
I rebuilt ojaswiat.com from Next.js to Nuxt. Here's what I used:
Modules That Made It Easy
1. @nuxt/content - Built-in CMS
// Query blogs with type safety
const blogs = await queryCollection('blogs')
.where('primary_category', '==', 'algorithms')
.order('published_at', 'desc')
.limit(10)
.all();
No database needed. Markdown files with frontmatter. Type-safe queries. Incredible.
2. @nuxt/ui - Component Library
<UCard>
<template #header>
<h3>{{ blog.title }}</h3>
</template>
<p>{{ blog.description }}</p>
<template #footer>
<UButton>Read More</UButton>
</template>
</UCard>
Beautiful components out of the box. Dark mode support. Fully accessible.
3. @pinia/nuxt - State Management
// stores/AlertStore.ts
export const useAlertStore = defineStore('alert', () => {
const alerts = ref<Alert[]>([]);
function addAlert(message: string, type: AlertType) {
alerts.value.push({
id: Date.now(),
message,
type,
});
}
return { alerts, addAlert };
});
Used across the entire app. No provider setup needed.
4. @nuxtjs/seo - SEO Meta Tags
// Automatic sitemap, meta tags, OG images
useSeoMeta({
title: blog.title,
description: blog.description,
ogImage: blog.image,
});
One line in nuxt.config.ts. Everything configured.
Performance: Vue Site is Faster
Metrics:
- Lighthouse Score: 98 (was 92 in Next.js)
- First Contentful Paint: <1s (was ~1.5s)
- Time to Interactive: <2s (was ~3s)
- Bundle Size: 20% smaller
Why?
- Nuxt's automatic code splitting
- Component Islands (selective hydration)
- Better module tree-shaking
- Optimized runtime
What I Miss from React (Yes, There Are Things)
Let me be balanced—React has advantages:
1. Ecosystem Size
React's ecosystem is massive:
- More third-party libraries
- More Stack Overflow answers
- More tutorials and courses
- Easier to find developers
Vue's ecosystem is growing but smaller.
2. Job Market
More companies use React:
- More job opportunities
- Higher demand
- Better pay (sometimes)
But this is changing. Vue is growing rapidly.
When Should You Choose Vue Over React?
Choose Vue if:
1. You want better developer experience
- Less boilerplate
- Clearer separation of concerns
- Better tooling out of the box
2. You're building with a meta-framework Nuxt > Next.js for DX (in my opinion)
3. You value convention over configuration Vue and Nuxt make smart choices for you
4. You're building content-heavy sites@nuxt/content is incredible for blogs, documentation, portfolios
5. You want faster onboarding Juniors pick up Vue faster than React
Choose React if:
1. You need the biggest ecosystem : More packages, more solutions, more tutorials
2. Job market is a concern : More React jobs currently available
3. You need React Native : Mobile development is a priority
4. Your team knows React : Switching has costs
5. You like JSX : Some people prefer JSX over templates
6. You want to regret front-end development : If you want to regret front-end development, then React is for you.
My Current Stack (All Vue/Nuxt)
After 6 months with Vue:
Personal Projects:
- Portfolio website (Nuxt 3)
- Blog platform (Nuxt Content)
- Side projects (Vue 3 + Vite)
Work:
- Production applications (Vue 3 + TypeScript)
- Internal tools (Nuxt 3)
- Component libraries (Vue 3)
I'm not going back to React for personal projects. The developer experience is too good.
Tips for Migrating from React to Vue
If you're considering the switch:
1. Learn Composition API, Not Options API
<!-- Use this (Composition API) -->
<script setup lang="ts">
const count = ref(0);
const double = computed(() => count.value * 2);
</script>
<!-- Not this (Options API) -->
<script>
export default {
data() {
return { count: 0 };
},
computed: {
double() {
return this.count * 2;
}
}
}
</script>
Composition API is similar to React hooks. Start there.
2. Embrace SFCs (Single File Components)
Don't fight the template syntax. It's cleaner once you get used to it.
3. Use Nuxt, Not Just Vue
Nuxt is like Next.js but better. Start with Nuxt for full-stack apps.
4. Leverage Modules
Browse nuxt.com/modules. There's probably a module for what you need.
5. Forget React Patterns
Don't try to write React code in Vue. Learn Vue patterns:
- Use
v-modelfor forms - Use
computedfor derived state - Use
watchfor side effects - Embrace template directives
6. TypeScript from Day One
Vue 3 + TypeScript is excellent. Set it up immediately:
// Define component props with types
interface Props {
title: string;
count?: number;
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
});
The Verdict: Why I'm Never Going Back
After 6 months of Vue development:
Developer Experience: 10/10
- Faster to write
- Fewer bugs
- More enjoyable
- Better tooling
Ecosystem: 8/10
- Growing rapidly
- Nuxt modules are amazing
- Good enough for most projects
- Some gaps compared to React
Performance: 9/10
- Faster than my React apps
- Better optimizations
- Smaller bundles
Learning Curve: 8/10
- Easy if you know React
- Composition API familiar
- Templates take adjustment
- Documentation is excellent
Job Market: 6/10
- Fewer jobs than React
- But growing
- Higher quality positions
- Less competition
Overall: Would I switch again? Absolutely.
Conclusion: The Framework That Changed My Mind
I started using Vue because of a job requirement. I continue using Vue because it's better for how I work.
React is great. I'm not hating on React. But Vue's developer experience, Nuxt's amazing modules, and Pinia's simplicity won me over.
If you're a React developer:
- Try Vue for a side project
- Give Nuxt a chance
- Experience Pinia stores
- See if the DX improvements matter to you
If you're learning web development:
- Either framework is fine
- Vue might be easier to start with
- React has more jobs currently
- Pick based on your goals
For me? Vue is now my default choice. The framework I forced myself to learn became the framework I choose by default.
Sometimes the best things come from unexpected places. A job change forced me to learn Vue. Turns out, it was exactly what I needed.
If you're considering migrating from React to Vue or curious about Nuxt vs Next.js, I'd love to hear your thoughts! Connect with me on Twitter or LinkedIn and let's discuss our experiences!
Support My Work
If this guide helped you with this topic, I'd really appreciate your support! Creating comprehensive, free content like this takes significant time and effort. Your support helps me continue sharing knowledge and creating more helpful resources for developers.
☕ Buy me a coffee - Every contribution, big or small, means the world to me and keeps me motivated to create more content!
Photo by Eugene Zhyvchik on Unsplash