Back to blog
Opinion

Vue 3 in 2026: Why I'm Still All-In After All These Years

Kim BoenderKim Boender
March 30, 2026 8 min read

I'll be honest with you: I'm biased. I've been building with Vue since the early days, back when Vue 1.x was still a one-person side project and the rest of the world hadn't noticed yet. I watched it grow from a lightweight alternative to Angular into one of the most beloved frameworks in the JavaScript ecosystem, and then mature into the powerhouse that Vue 3.x is today.

This post is partly a love letter and partly a practical guide. I want to show you what Vue 3 looks like at its best in 2026: the latest version, the patterns that make it shine, and real code examples you can steal immediately.

A quick history, because context matters

Vue was created by Evan You, who released the first public version in February 2014. It grew organically through community love rather than corporate backing, no Google or Facebook money behind it, just a framework that developers genuinely enjoyed using.

I came to Vue relatively early, when it was still being dismissed by a lot of people as "just a nice template system." What drew me in was how approachable it felt. You could drop a script tag into an HTML file and have a working reactive component in five minutes. But it also scaled, the more you invested in learning it, the more expressive and powerful it became.

Vue 3 launched in September 2020. It was a ground-up rewrite: new reactivity system, TypeScript-first internals, the Composition API, <script setup>, better tree-shaking, Fragments, Teleport, Suspense. The migration was bumpy for a while, but the team got it right. Vue 3 is now the clear default, Vue 2 reached end-of-life at the end of 2023, and the ecosystem has fully caught up.

The current stable version is Vue 3.5.x, and it is genuinely excellent.

The Composition API: the heart of modern Vue

If you're still writing Vue with the Options API and haven't made the jump to the Composition API with <script setup>, this section is for you.

The Options API, with its data(), computed, methods, mounted blocks, is not wrong or bad. It's still fully supported. But the Composition API unlocks a fundamentally better way to organize code, especially as components grow complex.

Here's the same component written both ways:

<!-- Options API (old school) -->
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
export default {
  data() { return { count: 0 } },
  computed: { doubled() { return this.count * 2 } },
  methods: { increment() { this.count++ } }
}
</script>
<!-- Composition API with <script setup> (modern Vue) -->
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() { count.value++ }
</script>

The <script setup> syntax is syntactic sugar over setup(). Everything you declare at the top level is automatically exposed to the template. No more this, no more mental overhead about object context, and your logic is just plain JavaScript functions.

Composables: the superpower of the Composition API

The real magic of the Composition API isn't just cleaner syntax. It's composables: reusable pieces of stateful logic that you can share across components.

Here's a composable I use on almost every project:

// composables/useFetch.ts
import { ref, watchEffect } from 'vue'

export function useFetch<T>(url: string | (() => string)) {
  const data = ref<T | null>(null)
  const error = ref<Error | null>(null)
  const loading = ref(false)

  watchEffect(async () => {
    const resolvedUrl = typeof url === 'function' ? url() : url
    loading.value = true
    error.value = null
    try {
      const res = await fetch(resolvedUrl)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      data.value = await res.json()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  })

  return { data, error, loading }
}

Compare this to the old mixins approach in Vue 2: no name collisions, no magic property injection, clear data flow. This is the pattern I missed before the Composition API existed.

defineModel: the v-model improvement (Vue 3.4+)

In Vue 3.4, the team shipped defineModel(), a macro that dramatically simplifies how you write components that use v-model. This was one of the best quality-of-life improvements in recent Vue history.

Before 3.4:

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input :value="modelValue" @input="emit('update:modelValue', $event.target.value)" />
</template>

With defineModel():

<script setup>
const model = defineModel()
</script>

<template>
  <input v-model="model" />
</template>

That's it. defineModel() returns a ref that automatically syncs with the parent's v-model. You can use it directly with v-model on native inputs.

useTemplateRef: typed template refs (Vue 3.5)

Template refs used to have a slightly awkward API. In Vue 3.5, useTemplateRef() was introduced to make them explicit and properly typed.

<script setup>
import { useTemplateRef, onMounted } from 'vue'

const inputEl = useTemplateRef<HTMLInputElement>('inputEl')

onMounted(() => {
  inputEl.value?.focus()
})
</script>

<template>
  <input ref="inputEl" />
</template>

The TypeScript generic gives you proper autocomplete on inputEl.value. Small improvement, big developer experience win.

Reactive props destructure (Vue 3.5)

This one removes a persistent gotcha that tripped up newcomers for years. In Vue 3.5, you can destructure props reactively:

<script setup>
// Vue 3.5+: destructured props ARE reactive
const { count = 0 } = defineProps<{ count?: number }>()
</script>

You can even set default values inline, replacing the withDefaults wrapper entirely.

Teleport: rendering outside your component tree

One of my favourite Vue 3 features that doesn't get enough credit is Teleport. It lets you render a piece of template somewhere else in the DOM, typically the body, while keeping the component logic where it belongs.

Classic use case: a modal that needs to render at the top of <body> to avoid CSS stacking context issues, but whose open/close state lives inside a component deep in your tree.

<script setup>
import { ref } from 'vue'
const isOpen = ref(false)
</script>

<template>
  <button @click="isOpen = true">Open Modal</button>
  <Teleport to="body">
    <div v-if="isOpen" class="modal-overlay" @click.self="isOpen = false">
      <div class="modal">
        <h2>Hello from a teleported modal!</h2>
        <button @click="isOpen = false">Close</button>
      </div>
    </div>
  </Teleport>
</template>

No portals library needed. No manual DOM manipulation. Vue handles it cleanly.

Pinia: the official state management solution

If you're using Vuex, please migrate to Pinia. Pinia has been the officially recommended state management library for Vue since 2022, and it is better in every way: simpler API, TypeScript out of the box, proper devtools integration, no mutations, no nested modules.

// stores/useUserStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  const isLoggedIn = computed(() => user.value !== null)

  async function login(email: string, password: string) {
    user.value = await api.login(email, password)
  }

  function logout() {
    user.value = null
  }

  return { user, isLoggedIn, login, logout }
})

Notice it uses the Composition API syntax: ref, computed, functions. Your stores look and feel like composables. No mapState, no mapGetters, no commit. Just a reactive object.

The ecosystem

Vue doesn't exist in a vacuum. These are the tools I use with it on every serious project.

Vite is the official build tool recommended by Vue. Instant dev server starts, lightning-fast HMR, ES module-native. I will never go back to webpack.

Vue Router supports the Composition API via useRouter() and useRoute(), with full TypeScript support.

VueUse is a massive collection of ready-made composables. useStorage, useDark, useIntersectionObserver, useDebounce, and over 200 more. If you're writing a composable from scratch, check VueUse first.

Nuxt 3 adds file-based routing, auto-imports, server routes, and more on top of Vue 3. If you need SSR, SSG, or a full-stack framework, Nuxt 3 is mature, fast, and excellent.

Vue 3.5 at a glance: the feature highlight reel

Here's a quick reference of the biggest improvements across recent Vue 3.x releases.

Vue 3.3 brought generic components with defineProps<T>() complex types, defineEmits complex type support, and defineSlots for typed slot definitions.

Vue 3.4 shipped the defineModel() macro, a 2x faster template compiler rewrite, improved reactivity tracking, and the v-bind same-name shorthand (:id instead of :id="id").

Vue 3.5 introduced reactive props destructuring, useTemplateRef() for typed template refs, useId() for generating unique accessible IDs, lazy hydration strategies for components, and memory usage improvements in large reactive trees.

The lazy hydration strategies in 3.5 are particularly interesting for SSR/Nuxt users. You can now defer hydrating heavy components until they're visible in the viewport, giving you significantly faster Time to Interactive on content-heavy pages.

Wrapping up

Vue 3.5 is the best Vue has ever been, and I say that having been here for most of the journey. The Composition API is mature, the tooling is excellent, the ecosystem is healthy, and the framework is fast in a way that rarely requires you to think about it.

If you're already a Vue developer, I hope the code examples in this post gave you something to take back to your projects, especially defineModel, reactive props destructure, and useTemplateRef if you haven't tried them yet.

If you're considering Vue for a new project: yes. Build the thing. The learning curve is gentle, the ceiling is high, and the developer experience is, in my completely unbiased opinion, among the best in the JavaScript ecosystem.

Frequently Asked Questions

Should I use the Options API or Composition API for a new Vue 3 project? +
For any new Vue 3 project, use the Composition API with script setup. The Options API is still fully supported and won't be removed, but the Composition API enables better code reuse via composables, more natural TypeScript integration, better tree-shaking, and scales much more cleanly as components grow complex. If you're coming from Vue 2 or teaching Vue to beginners, the Options API is a gentler on-ramp — but for production code in 2026, Composition API with script setup is the clear recommendation.
What's the difference between ref() and reactive() in Vue 3? +
ref() wraps a value (primitive or object) in a reactive container accessed via .value. reactive() makes a plain object deeply reactive without .value access, but only works for objects (not primitives). In practice, most Vue developers default to ref() for everything — it works for primitives, objects, and arrays alike, and the .value pattern is consistent. reactive() is useful when you want to group related state together and access it without .value, but it has a gotcha: destructuring a reactive object loses reactivity. When in doubt, use ref().
Is Vuex still worth using, or should I switch to Pinia? +
Switch to Pinia. Pinia is the officially recommended state management solution for Vue 3 and has been since 2022. It offers a much simpler API (no mutations, no nested modules), first-class TypeScript support, a great devtools experience, and uses the same Composition API patterns as your components. Vuex 4 technically works with Vue 3 but is in maintenance mode — no new features are being added. If you're starting a new project, use Pinia. If you have an existing Vuex codebase, the migration guide is straightforward.
What's the best way to deploy a Vue SPA in 2026? +
For a static Vue SPA (vite build output), Vercel is the simplest and fastest option — connect your GitHub repo, and you're live in minutes with automatic HTTPS and preview URLs per branch. Remember to add a vercel.json with a rewrite rule for Vue Router. For a full-stack setup (Vue + a Node.js or backend API), Railway is excellent — it handles both frontend and backend in one project with usage-based pricing. For maximum control or self-hosting, a DigitalOcean Droplet with Nginx as a reverse proxy is a solid, affordable choice.
Vue vs React in 2026 — which should I choose? +
Both are excellent choices and the technical gap between them has narrowed significantly. Vue tends to win on approachability, built-in solutions (router, state management), and template ergonomics. React wins on ecosystem size, job market dominance, and server-first features via React Server Components (Next.js). If you're building a new SPA or mid-size web app and want the fastest path to productivity with a great developer experience, Vue 3 with Vite is hard to beat. If team size, hiring, or Next.js-specific SSR features are a priority, React is a perfectly valid choice. Pick the one that makes your team productive and stop agonising over it.

Try it yourself

JSON Formatter

Format, validate, and beautify JSON instantly

Open JSON Formatter