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. 🤷

Learn more

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 of watch 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.