What Vue Is
Vue is a progressive framework for building user interfaces. Progressive means you can start small, adding Vue to a single part of an existing page with a CDN script tag, and scale up to a full single-page application with routing, state management, and server-side rendering. You choose how much framework you need.
This flexibility is not an accident. It is Vue's core design philosophy.
The Progressive Philosophy
Most frameworks demand commitment upfront. React needs a build step, JSX compilation, and a bundler. Angular requires TypeScript, decorators, modules, and a CLI. Vue lets you start with a script tag:
<div id="app">
<button @click="count++">Clicked {{ count }} times</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const count = ref(0);
return { count };
}
}).mount('#app');
</script>
No build tool. No npm install. No configuration files. You write this in a plain HTML file, open it in a browser, and it works.
When your project grows, you add a build step with Vite. Then you add Vue Router for client-side navigation. Then Pinia for state management. Then Nuxt for server-side rendering. Each layer is optional. Each layer integrates cleanly because they are all designed to work together.
How Vue Differs from React
Vue and React solve the same problem but make different tradeoffs.
Templates vs JSX
React uses JSX, which mixes JavaScript and HTML-like syntax inside your component functions. Vue uses HTML-based templates that feel like enhanced HTML:
<script setup lang="ts">
import { ref } from 'vue'
const items = ref(['Apples', 'Bananas', 'Cherries'])
const newItem = ref('')
function addItem() {
if (newItem.value.trim()) {
items.value.push(newItem.value.trim())
newItem.value = ''
}
}
</script>
<template>
<div>
<input v-model="newItem" placeholder="Add item" @keyup.enter="addItem" />
<ul>
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
</div>
</template>
Compare the React equivalent:
function ItemList() {
const [items, setItems] = useState(['Apples', 'Bananas', 'Cherries']);
const [newItem, setNewItem] = useState('');
function addItem() {
if (newItem.trim()) {
setItems([...items, newItem.trim()]);
setNewItem('');
}
}
return (
<div>
<input
value={newItem}
onChange={e => setNewItem(e.target.value)}
onKeyUp={e => e.key === 'Enter' && addItem()}
placeholder="Add item"
/>
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
Vue's v-model handles two-way binding in one directive. React requires you to wire up value and onChange manually. Vue's v-for reads like a sentence. React uses map() inside JSX. Neither approach is objectively better, but Vue's templates are more approachable for developers coming from HTML.
Two-Way Binding
React enforces one-way data flow. State flows down through props, events flow up through callbacks. Vue provides v-model for two-way binding, which is syntactic sugar over a prop and an event. This is especially useful for forms:
<script setup lang="ts">
import { ref } from 'vue'
const email = ref('')
const password = ref('')
</script>
<template>
<form @submit.prevent="handleLogin">
<input v-model="email" type="email" />
<input v-model="password" type="password" />
<button type="submit">Login</button>
</form>
</template>
Opinionated Defaults
Vue ships with an official router, state manager, and testing utilities. React leaves these choices to you. Vue's opinionated defaults reduce decision fatigue: you do not need to evaluate five routing libraries or three state managers before building your first page.
Vue 3: The Modern Era
Vue 3, released in 2020, was a ground-up rewrite that introduced three major changes.
Composition API
The Composition API replaced the Options API as the recommended way to write components. Instead of organizing code by option type (data, methods, computed, watch), you organize by logical concern:
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
const users = ref<{ id: number; name: string; active: boolean }[]>([])
const searchQuery = ref('')
const filteredUsers = computed(() =>
users.value.filter(u =>
u.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
)
const activeCount = computed(() =>
users.value.filter(u => u.active).length
)
onMounted(async () => {
const response = await fetch('/api/users')
users.value = await response.json()
})
</script>
<template>
<input v-model="searchQuery" placeholder="Search users..." />
<p>{{ activeCount }} active users</p>
<ul>
<li v-for="user in filteredUsers" :key="user.id">{{ user.name }}</li>
</ul>
</template>
Proxy-Based Reactivity
Vue 2 used Object.defineProperty to make data reactive, which had limitations: you could not detect property additions or array index mutations. Vue 3 uses JavaScript Proxies, which intercept all operations on an object. Reactivity just works without workarounds.
TypeScript Support
Vue 3 is written in TypeScript and provides first-class type inference. Props, emits, slots, and composables are all fully typed without extra effort when using script setup.
When Vue Is the Right Choice
Vue works well for:
- Teams with mixed experience levels where approachability and gentle learning curves matter.
- Projects migrating from jQuery or server-rendered apps where progressive adoption reduces risk.
- Applications heavy on forms and data binding where
v-modelreduces boilerplate significantly. - Teams that prefer opinionated defaults over assembling a custom stack from independent libraries.
- Full-stack applications with Nuxt where SSR, API routes, and deployment are handled by a single framework.
Vue is less ideal when your team is already experienced with React and its ecosystem, or when you need the largest possible pool of third-party component libraries.
Common Pitfalls
- Mixing Options API and Composition API without a clear reason: Pick one style for your project and stick with it. The Composition API with
script setupis the modern standard. - Assuming Vue is "React but easier": Vue has its own patterns and idioms. Trying to write React patterns in Vue leads to fighting the framework.
- Overlooking Nuxt for full applications: Vue alone handles the view layer. For routing, SSR, API integration, and deployment, Nuxt is the standard answer. Most production Vue apps use Nuxt.
- Underestimating the reactivity system: Vue's reactivity is deep and powerful. Learning how
ref,reactive,computed, and watchers interact is essential before building complex features. - Ignoring the ecosystem cohesion: Vue's strength is its unified ecosystem. Using Vue Router, Pinia, Vue DevTools, and Vite together gives you a polished experience that you lose when substituting third-party alternatives.
Key Takeaways
- Vue is a progressive framework: start with a CDN script tag, scale to a full SPA with routing, state management, and SSR.
- Templates with directives like
v-model,v-for, andv-ifprovide an approachable alternative to JSX. - Two-way binding with
v-modelreduces form boilerplate compared to React's one-way data flow. - Vue 3 introduced the Composition API, Proxy-based reactivity, and first-class TypeScript support.
- The official ecosystem (Vue Router, Pinia, Nuxt, Vite) is cohesive and reduces decision fatigue.
- Vue is an excellent choice for teams that value approachability, progressive adoption, and opinionated defaults.