When I first dipped my toes into Vue.js, it was a bit of a rollercoaster. Coming from the React world, things felt… familiar but just different enough to be confusing. v-for
instead of map()
? ref()
and reactive()
instead of useState()
? My brain definitely needed a minute (or several) to adjust.
But here’s the thing: once I pushed through the initial friction, Vue clicked. The more I built with it, the more I started appreciating its design choices—and eventually, it became a go-to tool in my freelance toolkit.
Today, I want to share that journey: the wins, the woes, and how Vue eventually won me over.
My Vue Setup
Let’s start with what I’m working with. My current Vue 3 stack looks like this:
- Vite – Ridiculously fast dev server
- Pinia – State management that actually makes sense
- Vue Router – Gets the job done (mostly)
- TypeScript – Because I love catching bugs before they bite
It’s a solid combo—but like any setup, it’s had its quirks. Let’s talk about some of the challenges I ran into and how I worked through them.
The Learning Curve: Vue's Reactivity System
Coming from React, I had to unlearn a lot.
In Vue, state and reactivity are handled differently. ref()
, reactive()
, watch()
, watchEffect()
—it felt like learning to speak a new dialect of JavaScript.
At first, I tried mapping everything to React equivalents. Spoiler: that didn’t work. Vue has its own philosophy, especially around reactivity.
What Helped
Honestly? Just building stuff. The more I experimented, the more things made sense. Vue’s documentation was a lifesaver, and small side projects helped me test ideas without the pressure of client deadlines.
State Management: Why Pinia Won Me Over
Ah, global state. Every dev’s favorite topic. 😅
I was a little wary when it came to managing shared state in Vue. I’d heard horror stories about Vuex, but then I discovered Pinia—and it changed everything.
It's like Vuex but… actually enjoyable to use. Here's a peek at my cart store setup:
import { defineStore } from "pinia";
export const useCartStore = defineStore("cart", {
state: () => ({
items: [],
loading: false,
error: null,
}),
getters: {
itemCount: (state) => state.items.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: (state) => state.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
},
actions: {
async fetchCart() {
try {
this.loading = true;
this.items = await fetchCartFromAPI();
} catch (err) {
this.error = err instanceof Error ? err.message : "Oops, something went wrong.";
} finally {
this.loading = false;
}
},
},
});
Why I love it:
- TypeScript-friendly out of the box
- Great DX with auto-complete
- No boilerplate
Routing Woes: Dynamic Routes & Nesting
Vue Router is powerful, but I’ll be real—it took a minute to wrap my head around dynamic routes and nested navigation.
Coming from frameworks like Next.js where file-based routing is the default, manually defining routes felt… a bit dated.
How I Solved It
Here’s my typical setup:
import { createRouter, createWebHistory } from "vue-router";
import Home from "@/views/Home.vue";
import Product from "@/views/Product.vue";
const routes = [
{ path: "/", component: Home },
{ path: "/product/:id", component: Product, props: true },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
It works well enough, but if Vue Router ever introduces file-based routing like Nuxt, I’d be very interested. 👀
Of course, Vue can handle file-based routing, too. It just requires a bit more setup. 🤷
Performance Tuning: Getting Snappy
When I started working with large datasets, I noticed a few hiccups—slow renders, sluggish updates, and other small annoyances.
What Helped
- Lazy loading components to improve initial load times
-
Using
computed
instead ofwatch
where it made sense - Performance tracking in dev with a simple toggle:
import { createApp } from "vue";
import App from "@/App.vue";
const app = createApp(App);
// Performance optimization
app.config.performance = import.meta.env.DEV; // Only enable performance tracking in development
// Compiler options for better performance
app.config.compilerOptions = {
comments: false, // Remove comments from templates in production
delimiters: ["${", "}"], // Use alternative delimiters to avoid conflicts with other template engines
whitespace: "condense", // Remove unnecessary whitespace
};
These small adjustments made a huge difference—especially for client-facing apps where speed = credibility.
The Verdict: What I love (and what...?) About Vue
What I Love:
- Component-based structure makes scaling a breeze
- Reactive system feels natural once you "get it"
- Pinia is a state management dream
- Vue’s ecosystem (with Vite + TypeScript) is seriously powerful
What Still Bugs Me:
- Initial setup can be intimidating, especially with TypeScript + routing
- Pinia can feel like overkill for super simple apps
- Reactivity can be a bit too eager—requires finesse
Final Thoughts: Would I Use Vue Again?
You bet.
Vue has grown on me in a big way. Once I pushed past the syntax curveballs and routing quirks, I found a toolset that’s powerful, flexible, and surprisingly enjoyable to work with.
Would I still use Next.js for certain projects? Absolutely. Especially when routing simplicity is key.
But when the job calls for a fast, lightweight, reactive frontend—and the client’s already Vue-friendly? Vue 3 is a great choice.