Skip to main content

Quick Start

This guide will walk you through creating your first Vue 3 application from scratch. We’ll use real code patterns from the Vue.js core repository (packages/vue/examples/).
Before starting, make sure you have Node.js version 20.0.0 or higher installed, as specified in the root package.json.

Create a Vue Application

1

Scaffold your project

Use the official create-vue tool to set up a new project with Vite:
npm create vue@latest
This will prompt you with several options:
✔ Project name: my-vue-app
✔ Add TypeScript? › No / Yes
✔ Add JSX Support? › No / Yes
✔ Add Vue Router? › No / Yes
✔ Add Pinia? › No / Yes
✔ Add Vitest? › No / Yes
✔ Add ESLint? › No / Yes
✔ Add Prettier? › No / Yes
For your first project, start with the defaults (No to all) to keep things simple.
2

Install dependencies

Navigate to your project directory and install dependencies:
cd my-vue-app
npm install
This installs [email protected] and development dependencies.
3

Start the development server

Run the development server:
npm run dev
Your app will be available at http://localhost:5173.

Your First Component

Let’s create a simple counter component using the Composition API. This example is inspired by Vue’s source examples.

Create the Component

Create src/components/Counter.vue:
<script setup>
import { ref } from 'vue'

// From packages/reactivity/src/ref.ts:58-64
// ref() takes an inner value and returns a reactive mutable ref object
const count = ref(0)

function increment() {
  count.value++
}

function decrement() {
  count.value--
}
</script>

<template>
  <div>
    <h2>Counter: {{ count }}</h2>
    <button @click="decrement">-</button>
    <button @click="increment">+</button>
  </div>
</template>

<style scoped>
button {
  margin: 0 5px;
  padding: 5px 15px;
  font-size: 16px;
}
</style>
The <script setup> syntax is a compile-time sugar for using the Composition API. It’s more concise than the traditional setup() function.

Use the Component

Update src/App.vue:
<script setup>
import Counter from './components/Counter.vue'
</script>

<template>
  <div id="app">
    <h1>My First Vue App</h1>
    <Counter />
  </div>
</template>

<style>
#app {
  font-family: 'Helvetica', Arial, sans-serif;
  text-align: center;
  margin-top: 60px;
}
</style>

Understanding Reactivity

Vue’s reactivity system automatically tracks dependencies and updates the DOM when reactive data changes.

Using ref()

From packages/reactivity/src/ref.ts, ref() creates a reactive reference:
import { ref } from 'vue'

// Create a reactive reference
const count = ref(0)

// Access the value with .value
console.log(count.value) // 0

// Modify the value
count.value++
console.log(count.value) // 1
In JavaScript, you must use .value to access or modify refs. In templates, refs are automatically unwrapped, so you can use {{ count }} instead of {{ count.value }}.

Using computed()

From packages/runtime-core/src/apiComputed.ts, computed values automatically recalculate when their dependencies change:
import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)

console.log(doubled.value) // 0
count.value = 5
console.log(doubled.value) // 10

Real-World Example: GitHub Commits Viewer

Let’s build a more complex example based on packages/vue/examples/composition/commits from the Vue source:
<script setup>
import { ref, watchEffect } from 'vue'

const API_URL = 'https://api.github.com/repos/vuejs/core/commits?per_page=3&sha='
const branches = ['main', 'v2-compat']
const currentBranch = ref('main')
const commits = ref(null)

const truncate = (v) => {
  const newline = v.indexOf('\n')
  return newline > 0 ? v.slice(0, newline) : v
}

const formatDate = (v) => v.replace(/T|Z/g, ' ')

// From packages/runtime-core/src/apiWatch.ts
// watchEffect automatically tracks reactive dependencies
watchEffect(() => {
  fetch(`${API_URL}${currentBranch.value}`)
    .then(res => res.json())
    .then(data => {
      commits.value = data
    })
})
</script>

<template>
  <div>
    <h1>Latest Vue.js Commits</h1>
    
    <template v-for="branch in branches" :key="branch">
      <input
        type="radio"
        :id="branch"
        :value="branch"
        name="branch"
        v-model="currentBranch"
      />
      <label :for="branch">{{ branch }}</label>
    </template>
    
    <p>vuejs/core@{{ currentBranch }}</p>
    
    <ul v-if="commits">
      <li v-for="{ html_url, sha, author, commit } in commits" :key="sha">
        <a :href="html_url" target="_blank" class="commit">
          {{ sha.slice(0, 7) }}
        </a>
        - <span class="message">{{ truncate(commit.message) }}</span><br />
        by
        <span class="author">
          <a :href="author_url" target="_blank">
            {{ commit.author.name }}
          </a>
        </span>
        at <span class="date">{{ formatDate(commit.author.date) }}</span>
      </li>
    </ul>
  </div>
</template>

<style scoped>
a {
  text-decoration: none;
  color: #f66;
}

li {
  line-height: 1.5em;
  margin-bottom: 20px;
}

.author, .date {
  font-weight: bold;
}
</style>
This example demonstrates several Vue features: reactive data with ref(), automatic dependency tracking with watchEffect(), template directives like v-for and v-model, and computed rendering.

Template Syntax

Vue uses HTML-based template syntax. From packages/vue/src/index.ts:30-102, templates are compiled into optimized render functions.

Interpolation

<template>
  <!-- Text interpolation -->
  <p>{{ message }}</p>
  
  <!-- JavaScript expressions -->
  <p>{{ count + 1 }}</p>
  <p>{{ ok ? 'YES' : 'NO' }}</p>
</template>

Directives

<template>
  <!-- Attribute binding -->
  <img :src="imageUrl" :alt="imageAlt" />
  
  <!-- Event handling -->
  <button @click="handleClick">Click me</button>
  
  <!-- Two-way binding -->
  <input v-model="text" />
  
  <!-- Conditional rendering -->
  <p v-if="isVisible">Visible content</p>
  <p v-else>Hidden content</p>
  
  <!-- List rendering -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

Composition API Patterns

The Composition API (from packages/runtime-core/src/apiDefineComponent.ts) provides flexible ways to organize component logic.

Setup Function

From packages/vue/examples/composition/commits:44-66, the setup() function is the entry point:
import { createApp, ref, watchEffect } from 'vue'

createApp({
  setup() {
    const currentBranch = ref('main')
    const commits = ref(null)

    watchEffect(() => {
      fetch(`${API_URL}${currentBranch.value}`)
        .then(res => res.json())
        .then(data => {
          commits.value = data
        })
    })

    return {
      branches: ['main', 'v2-compat'],
      currentBranch,
      commits,
      truncate,
      formatDate
    }
  }
}).mount('#demo')

Script Setup Syntax

The <script setup> syntax is simpler and more concise:
<script setup>
import { ref, watchEffect } from 'vue'

// Everything declared here is automatically exposed to the template
const currentBranch = ref('main')
const commits = ref(null)

watchEffect(() => {
  // Fetch logic here
})
</script>
Both syntaxes are equivalent. <script setup> is compile-time sugar that eliminates the need for an explicit return statement.

CDN Quick Start (No Build Step)

For simple use cases or learning, you can use Vue from a CDN. This example is from packages/vue/examples/composition/markdown:
<!DOCTYPE html>
<html>
<head>
  <title>Vue Quick Start</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
    <button @click="count++">Count: {{ count }}</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('Hello Vue!')
        const count = ref(0)

        return {
          message,
          count
        }
      }
    }).mount('#app')
  </script>
</body>
</html>
CDN builds are great for prototyping but not recommended for production. Use a build tool for production applications to enable:
  • Single-File Components (.vue files)
  • Tree-shaking and optimization
  • Hot Module Replacement
  • Pre-compiled templates

Build for Production

1

Build your application

npm run build
This creates optimized production files in the dist/ directory.
2

Preview the production build

npm run preview
Test your production build locally before deploying.
3

Deploy

Upload the dist/ folder to your hosting provider.

Key Takeaways

Reactive Data

Use ref() for primitive values and reactive() for objects. Access refs with .value in JavaScript.

Composition API

Use <script setup> for cleaner, more concise component code with automatic template exposure.

Template Syntax

Vue’s declarative templates use directives like v-if, v-for, v-model, and @click for common tasks.

Computed Values

Use computed() for derived state that automatically updates when dependencies change.

Next Steps

Now that you’ve built your first Vue application, explore these topics:
  • Components: Learn about props, events, and component communication
  • Routing: Add navigation with Vue Router
  • State Management: Manage complex state with Pinia
  • TypeScript: Add type safety to your Vue applications
  • Testing: Write unit tests with Vitest
All examples in this guide are based on real code from the Vue.js core repository at packages/vue/examples/composition/. You can find more examples there!

Build docs developers (and LLMs) love