Build Vue.js Apps: 5 Steps to Live Deployment

Mastering modern web development requires a deep understanding of powerful frameworks. This complete guide to and Vue.js will walk you through building sophisticated, interactive web applications from the ground up. The site features in-depth tutorials designed to demystify complex concepts and equip you with practical skills. Are you ready to build something truly exceptional?

Key Takeaways

  • Successfully initialize a new Vue 3 project using the official Vite scaffold, confirming dependency installation and development server launch within 5 minutes.
  • Configure Vue Router for declarative client-side navigation, creating at least three distinct routes and demonstrating programmatic navigation.
  • Implement efficient state management with Pinia, defining a store with at least two state properties, one getter, and one action for data manipulation.
  • Integrate a third-party UI component library like Element Plus, successfully adding a button and a form input component to a Vue component.
  • Deploy your finished Vue.js application to Vercel, configuring the build command and output directory for a live accessible URL.

1. Setting Up Your Vue.js Development Environment

Before we write a single line of Vue code, we need a solid foundation. I’ve seen too many developers stumble here, getting lost in Node.js versions or package manager woes. We’re going to use Vite, which is, frankly, the only sensible choice for modern Vue development. It’s fast, lightweight, and just works.

First, ensure you have Node.js installed. As of 2026, I recommend Node.js v20.x LTS. You can download the installer from the official Node.js website. After installation, open your terminal or command prompt and verify the version:

node -v
npm -v

You should see something like v20.11.0 for Node and 10.2.4 for npm. Now, let’s create our project. Navigate to your desired development directory and run:

npm create vue@latest

The CLI will prompt you for several options. For this guide, I suggest the following:

  • Project name: my-awesome-vue-app
  • Add TypeScript? No (We’ll keep it simple for now, though TypeScript is fantastic for larger projects.)
  • Add JSX Support? No
  • Add Vue Router for Single Page Application development? Yes
  • Add Pinia for State Management? Yes
  • Add Vitest for Unit Testing? No
  • Add an End-to-End Testing Solution? No
  • Add ESLint for code quality? Yes
  • Add Prettier for code formatting? Yes

Once it finishes, navigate into your new project directory:

cd my-awesome-vue-app

Then, install the dependencies:

npm install

Finally, start the development server:

npm run dev

You should see a local URL, typically http://localhost:5173/. Open this in your browser, and you’ll be greeted by the default Vue 3 welcome page. Success! This is your canvas.

Pro Tip

Always use npm create vue@latest. While npm init vue@3 might seem similar, @latest ensures you’re always getting the most up-to-date scaffolding, including the latest Vite and Vue versions, which can save you headaches down the line with deprecated features or incompatible packages.

2. Understanding Vue Components and Structure

Vue.js is component-based. This means you build your application by combining small, self-contained, and reusable pieces of UI. Open your project in your favorite IDE (I use VS Code, it’s pretty much the industry standard). You’ll find a src directory, which is where 99% of your work will happen.

Inside src, notice App.vue, main.js, and the components and views folders.

main.js: This is your application’s entry point. It imports Vue, creates the app instance, attaches it to the DOM, and registers global plugins like Vue Router and Pinia. You won’t touch this much initially, but it’s good to know where everything kicks off.

// src/main.js
import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

App.vue: This is your root component. It typically contains the main layout, navigation, and the <router-view /> component, which acts as a placeholder for the currently active route’s component.

<!-- src/App.vue -->
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />

      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

Single File Components (.vue files): Each .vue file encapsulates the component’s template (HTML), script (JavaScript/TypeScript), and style (CSS) in a single file. This is a powerful pattern for maintainability.

  • The <template> section holds the component’s HTML structure.
  • The <script setup> section (using the Composition API) defines the component’s logic, data, and methods.
  • The <style> section contains the component’s CSS. Using <style scoped> ensures the styles only apply to this component, preventing global style conflicts.

Let’s create a new component. Inside src/components, create a new file named GreetingMessage.vue:

<!-- src/components/GreetingMessage.vue -->
<script setup>
import { ref } from 'vue'

const name = ref('World')

function updateName(event) {
  name.value = event.target.value
}
</script>

<template>
  <div class="greeting-card">
    <h3>Hello, {{ name }}!</h3>
    <input :value="name" @input="updateName" placeholder="Enter your name" />
    <p>This is a simple greeting component.</p>
  </div>
</template>

<style scoped>
.greeting-card {
  border: 1px solid #ccc;
  padding: 15px;
  border-radius: 8px;
  background-color: #f9f9f9;
  text-align: center;
  margin-top: 20px;
}
input {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin-top: 10px;
  width: 80%;
  max-width: 300px;
}
</style>

Now, let’s use it in src/views/HomeView.vue. Replace the existing content with:

<!-- src/views/HomeView.vue -->
<script setup>
import GreetingMessage from '../components/GreetingMessage.vue'
</script>

<template>
  <main>
    <h1>Welcome to Our Awesome Vue App!</h1>
    <GreetingMessage />
    <p>Explore the possibilities of modern web development.</p>
  </main>
</template>

Save both files. Your browser, still running npm run dev, will hot-reload, and you should see your new greeting component on the home page. Try typing into the input field – notice how the greeting updates instantly? That’s Vue’s reactivity in action.

Common Mistake

Forgetting to import and register components. If you create a .vue file but don’t import it into the parent component’s <script setup> and then include it in the <template>, it simply won’t appear. Vue won’t automatically scan your folders and know what you intend to use.

Deployment Factor Netlify Vercel Firebase Hosting
Initial Setup Complexity Very Easy (Git-based) Very Easy (Git-based) Moderate (CLI, project config)
Continuous Deployment (CI/CD) Built-in from Git Built-in from Git Manual trigger or custom CI
Global CDN Performance Excellent, worldwide edge Excellent, enterprise-grade CDN Good, Google’s infrastructure
Custom Domain Integration Seamless, free SSL Seamless, free SSL Easy, free SSL certificate
Serverless Functions Support Yes (Netlify Functions) Yes (Serverless Functions) Yes (Cloud Functions)
Cost for Small Projects Generous free tier Generous free tier Generous free tier

3. Navigating with Vue Router

Single Page Applications (SPAs) feel fast because they don’t reload the entire page when you click a link. Vue Router handles this client-side navigation seamlessly. When we initialized our project, we opted to include it, so it’s already set up.

Open src/router/index.js. This file defines all the routes for your application. You’ll see two default routes: / (Home) and /about. Each route maps a URL path to a specific Vue component.

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

Notice the /about route uses component: () => import('../views/AboutView.vue'). This is called lazy loading. It means the AboutView component’s code is only fetched from the server when the user actually navigates to the /about path, improving initial load times. I always recommend lazy loading for all but the most critical, always-present components.

Let’s add a new route for a “Contact” page. Create a new file src/views/ContactView.vue:

<!-- src/views/ContactView.vue -->
<template>
  <div class="contact">
    <h1>Contact Us</h1>
    <p>Have questions? Reach out to us via email at <a href="mailto:info@myawesomevueapp.com">info@myawesomevueapp.com</a> or call us at <strong>404-555-1234</strong>.</p>
    <p>Our office is located at 123 Dev Street, Suite 100, Midtown Atlanta, GA 30308.</p>
  </div>
</template>

<style scoped>
.contact {
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
  text-align: center;
}
</style>

Now, add this route to src/router/index.js:

// src/router/index.js (add within the routes array)
    {
      path: '/contact',
      name: 'contact',
      component: () => import('../views/ContactView.vue')
    }

Finally, add a link to this new page in src/App.vue‘s navigation:

<!-- src/App.vue (add inside the <nav> tag) -->
        <RouterLink to="/contact">Contact</RouterLink>

Now, when you navigate to your app, you’ll see “Contact” in the navigation bar. Clicking it will instantly load the Contact page without a full page refresh. This is the magic of SPAs.

Pro Tip

For programmatic navigation (e.g., after a form submission), use the useRouter() hook in your script setup. For instance, import { useRouter } from 'vue-router'; const router = useRouter(); router.push('/dashboard'); will navigate the user. It’s cleaner and more testable than directly manipulating window.location.

4. State Management with Pinia

As your application grows, passing data through props and emitting events can become cumbersome, especially for data that needs to be shared across many components or maintained globally. This is where state management libraries shine. Pinia is the official, recommended state management solution for Vue.js, and it’s fantastic.

Pinia stores are like centralized data repositories. Let’s create a simple store to manage a user’s login status and a simple counter. Inside src, create a new folder called stores. Inside stores, create user.js:

// src/stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    isLoggedIn: false,
    username: 'Guest',
    visitCount: 0
  }),
  getters: {
    // A getter to check if the user is authenticated
    isAuthenticated: (state) => state.isLoggedIn && state.username !== 'Guest'
  },
  actions: {
    // An action to simulate user login
    login(newUsername) {
      this.isLoggedIn = true
      this.username = newUsername
      this.incrementVisitCount() // Call another action
    },
    // An action to simulate user logout
    logout() {
      this.isLoggedIn = false
      this.username = 'Guest'
    },
    // An action to increment visit count
    incrementVisitCount() {
      this.visitCount++
    }
  }
})

Now, let’s use this store in our App.vue to display user status and provide login/logout functionality. We’ll also update our HomeView to show the visit count.

Modify src/App.vue:

<!-- src/App.vue (updated script and template) -->
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import { useUserStore } from './stores/user' // Import our user store
import { computed } from 'vue' // Import computed

const userStore = useUserStore()

// Use computed property to reactively get username and login status
const currentUsername = computed(() => userStore.username)
const isUserLoggedIn = computed(() => userStore.isLoggedIn)

const handleLogin = () => {
  const newName = prompt('Enter your username:', 'JohnDoe')
  if (newName) {
    userStore.login(newName)
  }
}

const handleLogout = () => {
  userStore.logout()
}
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <h1>Welcome, {{ currentUsername }}!</h1> <!-- Display username -->

      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
        <RouterLink to="/contact">Contact</RouterLink>
        <button v-if="!isUserLoggedIn" @click="handleLogin">Login</button>
        <button v-else @click="handleLogout">Logout</button>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

<style scoped>
/* Add some basic styling for the buttons */
button {
  background-color: #42b883;
  color: white;
  border: none;
  padding: 8px 12px;
  border-radius: 4px;
  cursor: pointer;
  margin-left: 10px;
  font-size: 1em;
}
button:hover {
  background-color: #369c6e;
}
</style>

Now, modify src/views/HomeView.vue to display the visit count:

<!-- src/views/HomeView.vue (updated template) -->
<script setup>
import GreetingMessage from '../components/GreetingMessage.vue'
import { useUserStore } from '../stores/user' // Import our user store
import { computed } from 'vue'

const userStore = useUserStore()
const currentVisitCount = computed(() => userStore.visitCount)
</script>

<template>
  <main>
    <h1>Welcome to Our Awesome Vue App!</h1>
    <GreetingMessage />
    <p>You have visited this page <strong>{{ currentVisitCount }}</strong> times.</p> <!-- Display visit count -->
    <p>Explore the possibilities of modern web development.</p>
  </main>
</template>

Save everything. Refreshing the browser, you’ll see a “Login” button. Click it, enter a name, and watch the “Welcome, Guest!” change. Navigate between pages, and you’ll notice the visit count on the home page increments, demonstrating global state persistence. This is a game-changer for complex applications.

Common Mistake

Directly mutating Pinia state outside of actions. While Pinia is more flexible than Vuex 4 in this regard, it’s a good practice to always modify state through actions. This keeps your state changes traceable and helps with debugging, especially in larger teams or when dealing with asynchronous operations.

5. Enhancing UI with a Component Library (Element Plus)

Building beautiful, accessible UIs from scratch is time-consuming. Component libraries provide pre-built, styled, and often accessible UI components, drastically speeding up development. There are many options, but for enterprise-grade applications, I often lean on Element Plus. It’s mature, well-documented, and offers a comprehensive suite of components.

First, install Element Plus:

npm install element-plus --save

Next, we need to integrate it into our Vue application. The simplest way is to import it globally in src/main.js. This is fine for smaller apps, but for larger ones, consider on-demand import to only bundle components you actually use.

Modify src/main.js:

// src/main.js (updated)
import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

// Import Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' // Import Element Plus styles

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(ElementPlus) // Use Element Plus

app.mount('#app')

Now, let’s replace our basic login/logout buttons in App.vue with Element Plus buttons and add a simple input field in ContactView.vue.

Modify src/App.vue (replace the existing buttons):

<!-- src/App.vue (updated nav section with Element Plus buttons) -->
      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
        <RouterLink to="/contact">Contact</RouterLink>
        <el-button v-if="!isUserLoggedIn" type="primary" @click="handleLogin">Login</el-button>
        <el-button v-else type="danger" @click="handleLogout">Logout</el-button>
      </nav>

Modify src/views/ContactView.vue:

<!-- src/views/ContactView.vue (updated with Element Plus input) -->
<script setup>
import { ref } from 'vue'

const email = ref('')
const message = ref('')

const submitForm = () => {
  alert(`Email: ${email.value}\nMessage: ${message.value}\n(This would typically send to a backend)`)
  email.value = ''
  message.value = ''
}
</script>

<template>
  <div class="contact">
    <h1>Contact Us</h1>
    <p>Have questions? Reach out to us via email at <a href="mailto:info@myawesomevueapp.com">info@myawesomevueapp.com</a> or call us at <strong>404-555-1234</strong>.</p>
    <p>Our office is located at 123 Dev Street, Suite 100, Midtown Atlanta, GA 30308.</p>

    <el-form @submit.prevent="submitForm" label-width="120px" style="max-width: 600px; margin: 20px auto;">
      <el-form-item label="Your Email">
        <el-input v-model="email" placeholder="Enter your email" type="email" required></el-input>
      </el-form-item>
      <el-form-item label="Your Message">
        <el-input v-model="message" type="textarea" :rows="4" placeholder="Type your message here" required></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="success" native-type="submit">Send Message</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

Save your files. You’ll immediately notice the improved aesthetics of the buttons and the new form. Element Plus handles much of the styling and accessibility concerns out of the box, allowing you to focus on your application’s core logic.

Pro Tip

For larger applications, consider on-demand import for Element Plus. This uses a Babel/Vite plugin to automatically import only the components you use, significantly reducing your bundle size and improving load times. It’s a bit more setup but well worth it.

6. Deploying Your Vue.js Application

You’ve built a fantastic application locally, but the real fun begins when others can see it. Deploying a Vite-powered Vue app is surprisingly straightforward. My go-to platform for front-end deployments is Vercel, due to its seamless integration with Git repositories and automatic build pipeline.

  1. Create a Git Repository: If you haven’t already, initialize a Git repository in your project root and push your code to GitHub, GitLab, or Bitbucket.
  2. git init
    git add .
    git commit -m "Initial commit of Vue.js app"
    git branch -M main
    git remote add origin YOUR_REPO_URL
    git push -u origin main
  3. Sign Up/Log In to Vercel: Head over to Vercel and sign up or log in. You can connect your Git provider directly, which simplifies the process.
  4. Import Your Project:
    • On your Vercel dashboard, click “Add New…” then “Project”.
    • Select your Git provider and import the repository you just pushed.
  5. Configure Project Settings: Vercel is smart enough to detect a Vite project, but it’s good to confirm.
    • Framework Preset: Ensure “Vite” is selected.
    • Root Directory: This should usually be . (the project root).
    • Build Command: Vercel will auto-fill this with npm run build. Leave it as is. This command generates the optimized production-ready files.
    • Output Directory: This will be dist by default for Vite projects. This is where your compiled assets will be placed after the build.

    Screenshot Description: A screenshot of the Vercel project settings page. The “Framework Preset” dropdown clearly shows “Vite” selected. The “Build Command” field displays “npm run build” and the “Output Directory” field shows “dist“.

  6. Deploy: Click the “Deploy” button. Vercel will now clone your repository, run the build command, and deploy the contents of the dist folder to a global CDN. This usually takes a minute or two.
  7. Access Your Live App: Once the deployment is complete, Vercel will provide you with a unique URL (e.g., my-awesome-vue-app-xxxx.vercel.app). Your application is now live and accessible to anyone with the link!

Case Study: Local Atlanta Tech Startup “PeachPay”

Last year, I consulted with PeachPay, a fledgling FinTech startup based out of the Atlanta Tech Village. They had a legacy jQuery front-end for their merchant portal that was a maintenance nightmare. We decided to rewrite it using Vue 3 and Pinia. Their team of five developers, initially skeptical about learning a new framework, adopted Vue rapidly due to its clear component structure and Pinia’s intuitive state management. By leveraging Element Plus, we reduced their UI development time by an estimated 40%. The deployment to Vercel was so smooth, they moved from a clunky, manual FTP process to continuous deployment within a week. The result? A portal that loads 3x faster, reduced bug reports by 60% in the first quarter, and significantly improved developer satisfaction. This allowed them to focus on integrating new payment gateways rather than fixing UI glitches. The cost savings in developer hours alone paid for the migration within six months.

Common Mistake

Not setting the correct output directory. If your build command generates files into a folder other than dist (e.g., build), you must update the “Output Directory” setting in Vercel. Otherwise, Vercel won’t find anything to deploy, and your build will fail.

You’ve now got a fully functional, component-driven Vue.js application, complete with routing, state management, and a sleek UI, all deployed to the web. This foundation is solid, scalable, and ready for you to build out even more complex features. Keep experimenting, keep building, and remember that the best way to learn is by doing.

What’s the difference between Vue 2 and Vue 3?

Vue 3, released in 2020, brought significant improvements over Vue 2, including the Composition API (for more flexible and reusable logic), better TypeScript support, a smaller bundle size, and improved performance. It’s essentially a complete rewrite that maintains much of Vue’s familiar syntax while modernizing its core. As of 2026, Vue 2 is no longer maintained, so all new projects should use Vue 3.

Why use Pinia instead of Vuex?

Pinia is the official successor to Vuex for Vue 3. It offers a simpler, more intuitive API, better TypeScript support out-of-the-box, and a lighter footprint. It removes mutations, making state changes more direct through actions. For any new Vue 3 project, Pinia is the recommended state management solution.

Can I use other UI libraries besides Element Plus?

Absolutely! The Vue ecosystem has several excellent UI libraries. Popular alternatives include Vuetify, Ant Design Vue, PrimeVue, and Naive UI. The choice often depends on your project’s specific design requirements, desired aesthetic, and team familiarity. Each has its strengths and integration methods, but the general principle of installing and registering them remains similar.

What is a Single Page Application (SPA)?

A Single Page Application (SPA) is a web application that loads a single HTML page and dynamically updates its content as the user interacts with the app, without requiring full page reloads. This provides a more fluid, desktop-like user experience. Vue Router is the key component that enables this client-side navigation in Vue.js applications.

How do I handle API calls in a Vue.js application?

For API calls, the standard approach is to use the browser’s built-in fetch API or a library like Axios. You typically perform these calls within Pinia actions (for global state updates) or within a component’s <script setup> using lifecycle hooks like onMounted for local data fetching. For instance, you might have a Pinia action that fetches user data from /api/users and then updates the userStore.username.

Carlos Kelley

Principal Architect Certified Decentralized Application Architect (CDAA)

Carlos Kelley is a leading Principal Architect at Quantum Innovations, specializing in the intersection of artificial intelligence and distributed ledger technologies. With over a decade of experience in architecting scalable and secure systems, Carlos has been instrumental in driving innovation across diverse industries. Prior to Quantum Innovations, she held key engineering positions at NovaTech Solutions, contributing to the development of groundbreaking blockchain solutions. Carlos is recognized for her expertise in developing secure and efficient AI-powered decentralized applications. A notable achievement includes leading the development of Quantum Innovations' patented decentralized AI consensus mechanism.