Setting up a Vue project with Vite

Scaffold a Vue 3 app with create-vue, understand every generated folder, and wire aliases, the dev proxy and environment files.

Scaffold and run

# interactive: choose TypeScript, Router, Pinia, ESLint, Vitest
npm create vue@latest my-app

cd my-app
npm install
npm run dev        # Vite dev server with HMR
npm run build      # production bundle into dist/
npm run preview    # serve the built bundle locally to check it
  • Vite is the build tool: in development it serves files as native ES modules, so there is no bundle step and an edit appears in milliseconds.
  • Hot module replacement swaps the changed module in place. Component state survives an edit unless you changed the component's own setup signature.
  • create-vue only asks questions; it does not install anything. Run npm install yourself and read the printed next steps.
  • Router and Pinia are opt-in at scaffold time. Adding them later means installing the package and registering the plugin in main.js.
ScriptWhat it does
devDev server with HMR, usually on port 5173
buildType-check where configured, then emit the production bundle to dist/
previewServe dist/ so the real build is verified before deploying
test:unitVitest once; add -- --watch while developing
lintESLint with the Vue plugin over the project

What was generated

my-app/
  index.html            entry HTML; Vite injects the module script here
  vite.config.js        plugins, aliases, dev server and proxy
  .env                  shared env values, committed (never secrets)
  public/               copied verbatim to the build root
  src/
    main.js             createApp(App).use(router).use(pinia).mount('#app')
    App.vue             root component; usually just a RouterView
    assets/             imported by components, hashed by the bundler
    components/         reusable pieces that know nothing about routes
    views/              one component per route
    router/index.js     route table
    stores/             Pinia stores

Only index.html is a real entry point: Vite rewrites the module script it contains and leaves the rest of the markup to you. Files under public/ are copied as-is and referenced by absolute path; assets imported from code go through the bundler and get a content hash.

  • components/ holds pieces that receive props and render; views/ holds components a route can point at.
  • Name files in PascalCase (UserCard.vue) so imports and the name shown in devtools match.
  • Anything only one route needs can live next to that view instead of in a shared folder.

Aliases, proxy and environment

// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }
  },
  server: {
    port: 5173,
    proxy: {
      // /api/users -> http://localhost:8080/api/users, so no CORS in development
      '/api': { target: 'http://localhost:8080', changeOrigin: true }
    }
  }
})
# .env        committed, applies to every environment
VITE_APP_TITLE=Guides

# .env.local  gitignored, per-machine overrides
VITE_API_BASE=/api

# only VITE_ prefixed variables reach client code; read with import.meta.env
⚠️
Every VITE_ variable is inlined into the shipped JavaScript in plain text, so treat it as public. API keys, database URLs and private tokens belong on the backend, never in a .env file the front end reads.

FAQ

Vite or the Vue CLI?
The Vue CLI is in maintenance mode. create-vue plus Vite is the current official setup: faster dev feedback, far less configuration, and the same Vue 3 you would write either way.
Do I have to use TypeScript?
No. Plain JavaScript still gets runtime prop warnings, and you can add TypeScript to one file at a time later. TypeScript pays off most around props, stores and API responses, where the shape is the thing you get wrong.

Directives, lists and conditional rendering Production builds, environments and deployment

Last refreshed 2026-09-18.