Full-project lesson · needs Node.js, npm and real project files, so it does not run in the in-page playground.
npm run dev is for working. npm run build creates the files you put on a server. They are different programs with different goals, so always test the build before you ship.
Run it
npm run build
For the standard Vite React template (Vite 8.3), the real output was:
vite v8.3.4 building client environment for production...
transforming...
✓ 20 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.45 kB │ gzip: 0.29 kB
dist/assets/react-CHdo91hT.svg 4.12 kB │ gzip: 2.06 kB
dist/assets/vite-BF8QNONU.svg 8.70 kB │ gzip: 1.60 kB
dist/assets/hero-CLDdwZDr.png 13.05 kB
dist/assets/index-DykytF2W.css 4.10 kB │ gzip: 1.47 kB
dist/assets/index-D2sGXTia.js 222.44 kB │ gzip: 69.28 kB
✓ built in 218ms
(The hash parts of the file names and the sizes will differ in your project.) The result is a dist folder of plain static files. Nothing in it needs Node.js to run.
What the build does
- Bundles your many modules (and React) into a few files, so the browser makes fewer requests.
- Minifies the JavaScript and CSS: shorter names, no spaces or comments, and unused code is removed (tree shaking).
- Uses the production build of React, without the development warnings and checks. React’s own code is smaller and much faster. That is why you must measure performance on a production build, never on
npm run dev. - Adds a content hash to file names (
index-D2sGXTia.js). When a file changes, its name changes, so browsers and CDNs can cache every asset for a very long time, and still pick up new versions. - Splits code wherever you used dynamic
import()(Lazy Loading). One verified example producedindex-BS2MIXql.jsplus a separateHeavyPanel-CKINAcrt.jschunk. - Rewrites
index.htmlso it points at the hashed files. - Copies
public/intodistunchanged. - In a TypeScript project,
npm run buildfirst runstsc -b, so type errors fail the build.
Preview the production build
npm run preview
➜ Local: http://localhost:4173/
➜ Network: use --host to expose
This serves the dist folder locally. Use it to check the real thing: navigate around, open the Network tab, run a Lighthouse audit. It is a convenient check, not a production server; do not expose it to the internet.
Dev vs production: differences that surprise people
npm run dev | npm run build | |
|---|---|---|
| React | Development build: extra warnings, slower | Production build: faster and smaller |
| Strict Mode double render and effects | Yes | No |
import.meta.env.DEV / .PROD | true / false | false / true |
| Files | Many separate modules | A few hashed bundles |
| Environment values | Read from .env, .env.development | Read from .env, .env.production, and inlined |
| Errors | Overlay in the browser | Plain console errors, or a blank page |
| Paths | Root-relative | Depend on the base setting |
Some bugs appear only in production: a wrong base, a missing environment variable, an import written with the wrong letter case (Windows and macOS ignore case, Linux servers do not). That is why you should preview before you deploy.
Important build settings
In vite.config.js:
export default defineConfig({
plugins: [react()],
base: '/', // where the app is served from; see below
build: {
outDir: 'dist', // output folder
sourcemap: false, // true makes .map files for debugging
},
})
baseis the public path prefix for every asset. Use'/'when the app is at the site root, and'/my-app/'when it lives in a subfolder (for examplehttps://example.com/my-app/). A wrongbaseis the number one cause of “works locally, blank page when deployed”; see Deployment Fundamentals and Common Production Errors. You can also set it on the command line, for examplenpx vite build --base=/my-app/.- Source maps let browser tools show your original code for production errors. Upload them privately to an error-tracking service rather than serving them publicly, if your code is sensitive.
build.chunkSizeWarningLimitcontrols the warning for large chunks (the default is 500 kB after minification). Treat the warning as a hint to split code, not just something to silence.
Check the size
The size of the JavaScript users download matters most. The template above ships about 69 kB gzipped of JavaScript. Things that make it grow quickly: large libraries (charts, editors, date and icon libraries), importing a whole library instead of the parts you use, and a lot of code in the first page that most visits do not need. How to keep it small:
- Import only what you use (
import { format } from 'date-fns', not a whole utility kit). - Lazy-load big features and routes.
- Compare alternatives before adding a dependency (look up its size).
- Use a bundle analyzer plugin (several exist; pick one that supports your Vite version) to see what is inside the bundle.
- Check the gzip number (what is actually sent over the network when the server compresses, which it should).
A verified build step
Our projects were built with npx vite build and then opened in a real browser. The dist/index.html references hashed files, for example:
<script type="module" crossorigin src="/assets/index-D2sGXTia.js"></script>
If you deploy into a subfolder without setting base, that /assets/... path will be wrong, and the page will be blank.
Key points
npm run buildbundles, minifies, hashes and splits your code into the staticdistfolder;npm run previewserves it locally.- Development and production behave differently; measure and test the production build.
- Set
basefor subfolder deployments, and watch your bundle size.