Building for Production
A production build is the version of your React app that users actually download.
During development, React gives you helpful warnings, readable source maps, hot reloading, and extra checks. In production, the goal changes: ship the smallest reliable bundle, load it quickly, and make failures visible without exposing implementation details.
What a Production Build Does
Most React tooling, such as Vite, Next.js, Remix, Create React App, or custom Webpack setups, performs several build steps:
- transpiles modern JavaScript and JSX into browser-compatible code
- bundles modules into optimized assets
- minifies JavaScript and CSS
- replaces compile-time environment variables
- tree-shakes unused exports when possible
- fingerprints asset filenames for long-term caching
- generates static files that can be served by a CDN or web server
Example Vite commands:
npm run build
npm run previewnpm run build creates the production files. npm run preview serves those files locally so you can inspect behavior before deploying.
Development vs Production
Development mode optimizes for feedback.
Production mode optimizes for users.
Important differences:
- React warnings may disappear in production.
- Error messages may be shorter.
- Source maps may be disabled or uploaded privately.
- Code splitting changes network behavior.
- Environment variables are frozen at build time for most static React apps.
- Strict Mode development double-invocation does not happen in production.
Do not assume a feature works in production just because it worked in the dev server.
Build-Time Configuration
In a static React build, public environment variables are usually embedded during the build.
VITE_API_BASE_URL=https://api.example.com npm run buildThen:
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;If you change VITE_API_BASE_URL after the app is already built, the browser bundle will not automatically change. You must rebuild, unless your app loads runtime configuration from a separate endpoint or file.
Awareness note:
- Vite exposes only variables prefixed with
VITE_. - Create React App exposes only variables prefixed with
REACT_APP_. - Next.js exposes browser variables only when prefixed with
NEXT_PUBLIC_. - Never put secrets in frontend environment variables. They become visible to users.
Inspecting Bundle Output
After a build, inspect the output directory.
Common output folders:
- Vite:
dist - Create React App:
build - Next.js static export:
out
Look for:
- unexpectedly large JavaScript files
- duplicated dependencies
- source maps included when they should not be public
- images or fonts that were not optimized
- routes that fail on direct refresh
- incorrect API URLs
For Vite projects, a bundle analyzer plugin can show which packages dominate the bundle.
Code Splitting
React can lazy-load parts of the app.
import { Suspense, lazy } from "react";
const AdminPage = lazy(() => import("./AdminPage"));
export function App() {
return (
<Suspense fallback={<p>Loading page...</p>}>
<AdminPage />
</Suspense>
);
}Use code splitting for routes, dashboards, editors, charts, and admin features. Avoid splitting tiny components only to create extra network requests.
Edge case:
If a lazy chunk fails to load because the user has an old tab open after a deployment, show a recoverable error. A simple error boundary can offer a refresh button.
Source Maps
Source maps connect minified production code back to original source code. They are useful for debugging error reports, but they can expose source structure.
Common options:
- disable public source maps
- upload source maps privately to an error tracker
- keep source maps for staging only
Choose based on the app's risk level. Internal dashboards may tolerate public source maps. finance, healthcare, or proprietary apps usually should not.
Production Checklist
Before deploying:
- Run tests.
- Run linting and type checks.
- Build the app locally.
- Preview the built app.
- Test direct navigation to nested routes.
- Test refresh after login and logout.
- Verify environment variables.
- Check loading, empty, and error states.
- Check browser console errors.
- Confirm assets load with the expected cache headers.
Common Mistakes
- Shipping a dev build to production.
- Assuming environment variables are runtime variables.
- Exposing API keys or private tokens in the frontend bundle.
- Testing only the dev server, not the built files.
- Forgetting fallback routing for single-page apps.
- Ignoring bundle size until the app feels slow.
- Treating source maps as harmless.
Tricky Question
If a React app reads import.meta.env.VITE_API_URL, can the server change that value after the app is deployed?
Usually no. For a static build, that value was embedded into the JavaScript bundle during build time. To change it, rebuild the app or design a runtime configuration file that the app fetches when it starts.
Which statement about frontend environment variables is most accurate?
Practice Challenge
Build and inspect one React app.
Requirements:
- create a production build
- serve the built files locally
- verify one route by direct refresh
- log the configured API base URL in development only
- identify the largest generated JavaScript file
- write a short note explaining whether public source maps should be enabled
Recap
A production build is not just a faster development build. It is a compiled artifact with embedded configuration, optimized assets, and different runtime behavior. Always test the built output before deploying it.