Production Build
The dev server is for you; the production build is for users. One command turns your app into small, cached, optimised files — and a few settings make sure it works on the real server.
On this page · 6 min · Advanced
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Create and preview a production build
- Configure environment variables per mode
- Set the base path for sub-folder hosting
- Inspect and reduce bundle size
Explanation#
npm run build runs vite build: JSX is compiled, code is minified and tree-shaken, files get content hashes (index-3f9a1c.js) so browsers can cache them forever. Output goes to dist/.
npm run preview serves dist/ locally — always test the real build before deploying.
Environment variables: .env, .env.production and .env.development files. Only variables prefixed VITE_ reach the browser, via import.meta.env.VITE_API_URL. Never put secrets there — anything in the bundle is public.
If the app is served from a sub-folder (https://site.com/shop/), set base: "/shop/" in vite.config so asset URLs are correct.
Check bundle size with the build output and a visualiser (rollup-plugin-visualizer). Fix big chunks with lazy-loaded routes and lighter libraries.
Source maps (build.sourcemap) help debug production errors; upload them to your error tracker instead of serving them publicly if the code is sensitive.
Syntax#
npm run build # → dist/
npm run preview # test the build at http://localhost:4173
# .env.production
VITE_API_URL=https://api.shop.example.com
Code example: A production-ready vite.config.js#
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), 'VITE_');
return {
plugins: [react()],
base: env.VITE_BASE_PATH || '/', // e.g. "/shop/" for sub-folder hosting
build: {
outDir: 'dist',
sourcemap: mode === 'staging', // maps only where you need to debug
chunkSizeWarningLimit: 600,
rollupOptions: {
output: {
manualChunks: { react: ['react', 'react-dom', 'react-router'] }, // stable vendor chunk
},
},
},
server: {
proxy: { '/api': 'http://localhost:4000' }, // dev only: avoids CORS
},
};
});
// src/config.js
// export const API_URL = import.meta.env.VITE_API_URL; // set in .env.production
npm run build prints each output file with its size, e.g. dist/assets/index-B2k9xQ.js 142 kB │ gzip: 46 kB and dist/assets/react-Cq81a.js 190 kB │ gzip: 61 kB. dist/ contains index.html and the hashed assets, ready to upload.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- loadEnv reads VITE_ variables for the current mode (production, staging…).
- base controls the URL prefix for every asset — essential for sub-folder hosting.
- Source maps are produced only for staging builds here.
- manualChunks keeps React in its own long-cached file, so app updates do not re-download it.
- The dev proxy forwards /api to the backend during development only.
Real-world use case#
A company builds the same React app with npm run build -- --mode staging and --mode production; each mode reads its own .env file with a different API URL.
Common mistakes#
VITE_STRIPE_SECRET ends up in the JavaScript bundle for anyone to read. Only public values belong there.
Assets load from / instead of /shop/. Set base and the router’s basename.
Some bugs (env variables, base path, case-sensitive imports) only appear in the build. Run npm run preview.
Best practices#
- Preview every build locally
- Public config only in VITE_ variables
- Lazy-load routes to keep the main bundle small
- Long-cache hashed assets, never cache index.html
Remember this#
npm run build → dist/. npm run preview → test it.
VITE_ variables are public.
Sub-folder? Set base.
Practice exercise#
Your task: Create .env.development and .env.production files with different VITE_API_URL values and show how a component reads it.
Need a hint?
Show solution
# .env.development
VITE_API_URL=http://localhost:4000/api
# .env.production
VITE_API_URL=https://api.shop.example.com
# In code:
# const res = await fetch(`${import.meta.env.VITE_API_URL}/products`);
Mini quiz#
Which environment variables does Vite expose to browser code?
Want more? Practise all React MCQs · React interview questions
My notes#
Saved in this browser.