Skip to main content

Setup and single-file components — the smallest setup to start with

The command to create a Vue project is a single line. It does ask a few questions along the way, though, and what you pick changes what gets generated. Some things can be added later and some are easier to decide up front, so knowing what you will be asked keeps you from hesitating.

This chapter creates a project with create-vue, the official scaffold, and reads through what each generated file is for. After that it looks at the structure of the single-file component, Vue's unit of development.

What you'll learn in this chapter​

  • Judge what the create-vue prompts ask and which options to pick
  • Explain what the generated directories and config files are for
  • Understand how to use the three blocks of a single-file component

Creating a project​

This is the command the official Vue Quick Start points you to1.

npm create vue@latest

The Node.js requirement is ^22.18.0 || >=24.12.0. You need either 22.18.0 or later on the 22 line, or 24.12.0 or later, and the 23 line is out of scope. If you are on an odd-numbered line, switch.

You can also create a Vue project from a Vite template (vue-ts under npm create vite@latest), but what you get is minimal. create-vue combines the router, state management, type checking, and linter configuration for you, so you start closer to the setup you would use in production.

What the prompts ask​

The questions come in this order.

#QuestionDetails
1Project name (target directory)The directory to create. Skipped if you passed it as a command argument
2Package nameThe name in package.json. Skipped if the directory name can be used as is
3Use TypeScript?Whether to use TypeScript. Asked before the feature selection
4Select features to include in your projectMulti-select of features. See the table below
5Select an End-to-End testing frameworkOnly if you chose End-to-End Testing in 4. Playwright or Cypress
6Select experimental features to include in your projectMulti-select of experimental features. You can move on without picking any
7Skip all example code and start with a blank Vue project?Whether to strip the example code and start empty

The feature selection in question 4 offers seven options.

OptionDetailsHow this guide treats it
JSX SupportLets you write JSX instead of templatesNot covered
Router (SPA development)Adds Vue RouterCovered in chapter 15 and chapter 16
Pinia (state management)Adds the state management libraryCovered in chapter 17
Vitest (unit testing)Adds a unit testing setupNot covered in this guide. Vitest itself is covered in TypeScript Guide — Development toolchain
End-to-End TestingAdds Playwright or CypressNot covered
Linter (error prevention)Adds oxlint and ESLintNot covered
Prettier (code formatting)Adds code formattingNot covered

The experimental features in question 6 offer Vue 3.6 (Release Candidate) and an option to swap Prettier for Oxfmt. This guide takes the stable 3.5 as its baseline, so it picks neither. The question always appears when you go through the prompts, so if you do not need them, move on without selecting anything.

If you want to start with the smallest possible setup for learning, enabling only TypeScript and leaving the rest off is the clearest. This guide covers Router and Pinia later on, so if you are reading straight through, including them from the start keeps the diffs small.

Skipping the prompts​

Passing flags skips the prompts. This is useful in CI, or when recreating the same setup.

# Create a TypeScript + Router + Pinia setup without the prompts
npm create vue@latest my-app -- --ts --router --pinia

--default gives you a setup with no extra features, but TypeScript is still enabled. --bare tells it to omit the example code and is not a feature flag, so passing it on its own still starts the prompts.

Installing dependencies and starting up​

cd my-app
npm install
npm run dev

Reading what was generated​

Here is the layout when you pick TypeScript, Router, and Pinia.

my-app/
├── index.html The entry HTML. This is where #app lives
├── vite.config.ts Vite config. The @ → ./src alias is here too
├── env.d.ts Declarations for the types Vite injects
├── tsconfig.json A parent that only references the split configs
├── tsconfig.app.json For code that runs in the browser
├── tsconfig.node.json For code that runs in Node, such as config files
├── public/ Files served as-is without transformation
└── src/
├── main.ts Assembles the app and mounts it on #app
├── App.vue The top-level component
├── assets/ CSS and images. Transformed at build time
├── components/ Reusable components
├── views/ Screen-level components mapped to routes
├── router/ Route definitions
└── stores/ Pinia stores

This grows and shrinks with the features you pick. Choosing Linter adds eslint.config.ts and .oxlintrc.json; Prettier adds .prettierrc.json. Vitest adds vitest.config.ts and tsconfig.vitest.json, bringing the configs referenced by the parent to three.

composables/ is not generated. It is a directory for extracted logic, so you create it yourself when you need it (covered in chapter 8).

The entry point​

src/main.ts is where the app gets assembled.

src/main.ts
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')

createApp() creates the app instance, use() registers plugins, and mount() inserts it into #app in index.html. When you add a router or state management, you add another use() here.

Why tsconfig is split​

tsconfig.json contains nothing but references; the actual settings are split up. Code that runs in the browser and code that runs in Node, such as vite.config.ts, have different APIs and types available. Mixing them would let a reference to window pass in a place where window does not exist, or conversely leave Node APIs unusable.

The app-side tsconfig.app.json includes noUncheckedIndexedAccess. Types are assigned on the assumption that indexed access can yield undefined, so the type of const first = items[0] is string | undefined. Merely reading it is not an error, but using it somewhere that does not allow undefined, such as items[0].length, stops the build. It is a setting that errs on the safe side.

Type checking runs with npm run type-check. Underneath it is vue-tsc --build, which processes the split configs together. It uses vue-tsc rather than plain tsc because the contents of .vue files have to be parsed.

The commands you get​

CommandDetails
npm run devStarts the dev server
npm run buildRuns type checking and the build together, output to dist/
npm run build-onlySkips type checking and only builds
npm run previewServes the build output locally so you can check it
npm run type-checkRuns only the type check
npm run test:unitRuns unit tests with Vitest (if you chose Vitest)
npm run lintRuns oxlint and then ESLint (if you chose Linter)
npm run formatFormats with Prettier (if you chose Prettier)

Choosing Linter makes lint a two-stage setup that calls oxlint and then ESLint. The ESLint side is configured to disable rules that overlap with oxlint, so the same problem is not reported twice.

The three blocks of a single-file component​

A Vue component is a single file with a .vue extension, holding the template, the logic, and the styles in the same place.

src/components/Greeting.vue
<script setup lang="ts">
import { ref } from 'vue'

const name = ref('Vue')
</script>

<template>
<p class="greeting">Hello, {{ name }}</p>
</template>

<style scoped>
.greeting {
font-weight: bold;
}
</style>

The three blocks play these roles.

BlockRole
<script setup lang="ts">The component's logic. Variables and functions declared here are usable from the template
<template>The structure of the screen. HTML plus Vue's own notation
<style scoped>Styles for this component

The order is not fixed. What create-vue generates puts <script setup> first, so this guide follows that.

What script setup leaves out​

With <script setup>, anything declared at the top level is usable from the template. You do not need to write a return. Imported components are usable directly too, so no registration is needed.

Using a child component without registering it
<script setup lang="ts">
import Greeting from './Greeting.vue'
</script>

<template>
<Greeting />
</template>

What scoped does​

With <style scoped>, Vue assigns a unique attribute per component to the elements and adds that attribute to the selectors. This means you do not have to manage class name collisions yourself, so you can give each component plain, obvious names.

The scope is not "only inside this component". A child component that returns a single root element also gets the parent's attribute on that root element. If the child has its own <style scoped>, both attributes are applied; if it does not, only the parent's is. Either way, the parent's selector matches that root element. When the child returns multiple root elements, on the other hand, none of them receive the parent's attribute.

The parent's styles reaching a single-root child is useful when the parent wants to decide layout spacing. It can also change the child's appearance unintentionally, so it is worth keeping this route in mind.

To style something inside the child (deeper than its root), pierce through with :deep(). That does make the code depend on the child's internal structure, so overusing it makes things fragile under change.

What to do about Vue CLI​

Vue has an older webpack-based toolchain called Vue CLI, but the official position is that it is in maintenance mode. New projects are recommended to start with Vite, with an exception only where webpack-specific features are required2.

That does not mean projects already running on Vue CLI will break. But when creating something new, use create-vue.

Editor setup​

create-vue writes recommended extensions into .vscode/extensions.json. The central one is Vue - Official (identifier Vue.volar), which handles parsing and type information for .vue files. It used to be called Volar; it is now shipped as the official Vue extension.

Summary​

  • Create projects with npm create vue@latest. A Vite template works too, but the official scaffold is create-vue
  • Node must be 22.18.0 or later on the 22 line, or 24.12.0 or later; the 23 line is out of scope
  • The prompts ask whether to use TypeScript first, with the multi-select of features after that. The experimental-features question always appears in the interactive flow, so if you are not using the 3.6 candidate release, move on without selecting anything
  • Flags let you skip the prompts. Note that --default still enables TypeScript, and --bare on its own does not stop the prompts
  • tsconfig.json is split because the browser and Node have different APIs available. The app side includes noUncheckedIndexedAccess
  • Type checking is handled by vue-tsc. The contents of .vue files have to be parsed, so plain tsc is not enough
  • A single-file component has three blocks: <script setup>, <template>, and <style scoped>. Using <script setup> removes the need for return and for component registration
  • The reach of scoped is not limited to the inside of the component. A single-root child gets the parent's attribute on that root element (a multi-root child does not)
  • Vue CLI is in maintenance mode, not discontinued. Do not use it for new projects
Related references

Footnotes​

  1. Source: Quick Start — Creating a Vue Application. The Node requirement is on the same page. ↩

  2. Source: Tooling — Project Scaffolding. It describes Vue CLI as "currently in maintenance mode". On that basis it writes "It is recommended to start new projects with Vite", with the exception "unless specific webpack-only features are required". ↩