Vite's conventions differ from older tools, and knowing them avoids broken paths.
In a Vite project, `index.html` is the entry point and sits at the project root, not in public/. Vite reads it, follows <script type="module" src="/src/main.ts">, and builds the module graph from there. During build it rewrites the HTML with hashed asset URLs. Multi-page apps simply have several HTML files.
Typical layout:
index.html: entry; may reference/src/...paths, which are resolved from the project root.src/: your code and imported assets. Anything imported is processed, hashed and tree-shaken.public/: files copied as-is to the output root, served at/. Use it forrobots.txt,favicon.icoor files that must keep exact names. Reference them with absolute paths (/robots.txt) and never import them.vite.config.ts(optional): configuration.
The CLI:
vite(orvite dev): dev server on port 5173,--hostto expose it on your network,--opento launch a browser.vite build: production build intodist/.vite preview: servedist/locally on port 4173. Not a production server.
Scripts in package.json usually map dev, build and preview to these. For TypeScript projects, templates run tsc -b before vite build, because Vite only strips types and never type-checks.
Example
my-app/
├── index.html # entry point (at the root!)
├── public/
│ └── robots.txt # copied as-is -> /robots.txt
├── src/
│ ├── main.ts # <script type="module" src="/src/main.ts">
│ ├── App.tsx
│ └── assets/logo.svg # imported -> processed and hashed
├── vite.config.ts
└── package.json
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build", // Vite strips types, tsc checks them
"preview": "vite preview"
}Remember
- index.html at the root is the entry point
- Imported files are processed; public/ files are copied verbatim
- dev on 5173, preview on 4173
- Vite strips TypeScript types but never type-checks
Watch out for
- Importing files from public/, which bypasses hashing and breaks with a non-root
base.