Vue turns JavaScript state into a user interface and updates the affected DOM when that state changes. For a production-style Vue 3 project, the official quick start uses create-vue to scaffold a Vite-based application with Single-File Components. This lesson assumes basic HTML and JavaScript and ends with a small counter you can run and explain.
The important first idea is ownership: createApp starts one application at one mount element, the root component owns its reactive state, and the template describes what should be rendered from that state.
# Create Vue 3 project with Vite
npm create vue@latest my-vue-app
# Options you'll be asked:
# [ok] Add TypeScript? No (or Yes for TS)
# [ok] Add JSX Support? No
# [ok] Add Vue Router? Yes
# [ok] Add Pinia? Yes
# [ok] Add ESLint? Yes
cd my-vue-app
npm install
npm run dev # http://localhost:5173
# Project structure:
# src/
# assets/
# components/
# router/index.js
# stores/
# views/
# App.vue <- root component
# main.js <- entry point
<!-- src/App.vue - Single File Component (SFC) -->
<!-- Three sections: template, script, style -->
<template>
<!-- Template: HTML with Vue directives -->
<div class="app">
<h1>{{ message }}</h1>
<p>Count: {{ count }}</p>
<button @click="increment">+1</button>
<button @click="count = 0">Reset</button>
</div>
</template>
<script setup>
// Composition API with <script setup> (recommended)
import { ref } from 'vue'
const message = ref('Hello, Vue 3!')
const count = ref(0)
function increment() {
count.value++
}
</script>
<style scoped>
/* scoped: styles only apply to this component */
.app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
h1 { color: #42b883; } /* Vue green */
</style>
// src/main.js - Entry point
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia()) // state management
app.use(router) // routing
app.mount('#app') // mount to #app div in index.html
Vue 3 supports two API styles. Both are valid - choose based on preference and project needs.
<!-- Options API - familiar, object-based -->
<template>
<div>
<p>{{ fullName }} - {{ age }} years old</p>
<button @click="birthday">Happy Birthday!</button>
</div>
</template>
<script>
export default {
name: 'UserCard',
data() {
return {
firstName: 'Alice',
lastName: 'Smith',
age: 25
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
},
methods: {
birthday() {
this.age++
}
},
mounted() {
console.log('Component mounted')
}
}
</script>
<!-- Composition API with <script setup> - modern, flexible -->
<template>
<div>
<p>{{ fullName }} - {{ age }} years old</p>
<button @click="birthday">Happy Birthday!</button>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const firstName = ref('Alice')
const lastName = ref('Smith')
const age = ref(25)
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
function birthday() {
age.value++
}
onMounted(() => {
console.log('Component mounted')
})
</script>
Run npm create vue@latest and answer only the feature prompts your project needs. The generated project keeps application code in src, public files that must retain their names in public, and build configuration at the root. Run npm install once, then npm run dev for local development. The terminal prints the exact local URL rather than requiring a fixed port.
A no-build CDN script is useful for a tiny experiment or progressively enhancing server-rendered HTML. A scaffolded project is better once you need .vue files, imports, routing, testing, TypeScript, or an optimized production build. Pick the smallest setup that still matches the application you intend to maintain.
In <script setup>, ref creates a reactive holder for a primitive value. JavaScript reads and writes count.value, while a Vue template automatically unwraps the ref and uses count. An event listener such as @click changes the ref; Vue batches the update and patches the rendered text without reloading the page.
If the screen does not update, first verify that the app mounted to an existing element, the component imported the API it uses, and the value is reactive rather than an ordinary variable. Read the first browser-console error before changing configuration; a failed import or template expression often prevents the component from mounting at all.
Before deployment, run npm run build. Vite reports compile-time problems and writes optimized assets to the configured output directory. Test that output through an HTTP server, especially when the app uses client-side routes, because opening generated files directly does not reproduce server fallback behavior.
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.