The Project, File by File

index.html is the entry point and main.ts mounts the app: which files in a Vue project you edit, and which are generated.

SyntaxcreateApp(App).mount('#app')

A generated project has more files than you will ever edit. Knowing which is which keeps you from changing a file that is rewritten on every build.

The entry point is index.html

It sits in the project root, not in public/. Vite treats it as source: it reads the file, follows <script type="module" src="/src/main.ts"> and serves whatever that import graph needs. The file holds one empty element, usually <div id="app"></div>. You edit it to change the page title, the language attribute or a meta tag.

main.ts starts the application

createApp(App) creates an application instance from the root component and .mount('#app') renders it into that empty element. Global CSS is imported at the top. Plugins such as a router are registered here with app.use(), which Stage 3 covers.

src, public and the generated rest

src/App.vue is the root component and src/components/ holds the others. src/assets/ holds files you import from code: Vite processes them, adds a content hash and rewrites the URL. public/ holds files served unchanged at the site root, for a favicon.ico or a robots.txt. vite.config.ts configures the build, including the Vue plugin that compiles .vue files and the @ alias for src. The tsconfig files and env.d.ts exist for type checking. node_modules/ and dist/ are generated; leave them alone and keep both out of version control.

Example

Example · vue
<script setup lang="ts">
const appName = 'Dashboard'
</script>

<template>
  <header>
    <img src="./assets/logo.svg" width="48" height="48" alt="" />
    <img src="/favicon.ico" width="16" height="16" alt="" />
    <h1>{{ appName }}</h1>
  </header>
</template>

<style scoped>
header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
</style>

When to use it

  • Finding where to set the page title or add a meta tag in a Vue project
  • Deciding whether a new image belongs in src/assets or in public
  • Explaining to a teammate why editing a file in dist has no effect

More examples

index.html, the real entry point

Everything the user first downloads. The body is deliberately almost empty: Vue fills #app once main.ts runs. Edit this file for the title, the favicon link and meta tags, and nothing else; the build rewrites the script src to the hashed bundle for you.

Example · html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Dashboard</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

src/main.ts

The one place where the framework meets the page. createApp takes the root component and returns an application instance; mount takes a selector or a DOM element and can only be called once per instance. Anything that must happen before the first render, such as installing a plugin, goes between those two calls.

Example · ts
// src/main.ts
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

vite.config.ts

@vitejs/plugin-vue is what makes a .vue import work; without it the file is just unknown syntax. The alias lets you write import UserCard from '@/components/UserCard.vue' from any depth instead of counting ../ segments. Changing this file restarts the dev server.

Example · ts
// vite.config.ts
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)),
    },
  },
})

Discussion

  • Be the first to comment on this lesson.