React · Shipping

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.

Advanced 6 min Lesson 37 of 42Examples target React 19 with Vite.
Before you start:React Setup with Vite
0/42 in React
On this page · 6 min · Advanced

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#

Shell Illustrative
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#

JavaScriptvite.config.js Illustrative
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
Expected output
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#

  1. loadEnv reads VITE_ variables for the current mode (production, staging…).
  2. base controls the URL prefix for every asset — essential for sub-folder hosting.
  3. Source maps are produced only for staging builds here.
  4. manualChunks keeps React in its own long-cached file, so app updates do not re-download it.
  5. 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#

Secrets in VITE_ variables

VITE_STRIPE_SECRET ends up in the JavaScript bundle for anyone to read. Only public values belong there.

Blank page in a sub-folder

Assets load from / instead of /shop/. Set base and the router’s basename.

Deploying without previewing

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?
import.meta.env.VITE_API_URL.
Show solution
Shell Illustrative
# .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#

Production Build quizMultiple choice Question 1 of 3

Which environment variables does Vite expose to browser code?

Score so far: 0/0

Want more? Practise all React MCQs · React interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.