Part 7.5 — Build Optimization with Vite: Code Splitting & Env Handling
Vite’s biggest strength is that it gets out of your way.
That’s great for development—but production builds still need deliberate choices.
In this post, you’ll learn how to shape what your React app ships to users: how code is split, when it loads, and how environment variables flow safely from local dev to production.
1. Understand What Vite Actually Does
Vite has two phases:
Dev: native ES modules, instant reloads
Build: Rollup under the hood
Most optimization happens in the build phase. If you treat Vite as “magic,” you’ll miss important levers.
2. Start with the Default (It’s Good)
By default, Vite already gives you:
tree shaking
minification
hashed asset filenames
module preloading
Don’t fight this.
Optimize after you understand what’s shipped.
3. Code Splitting Starts with Routing
The easiest win is route-based splitting.
const UsersPage = React.lazy(() => import('./pages/UsersPage'));Use it in routes:
<Route
path="/users"
element={
<React.Suspense fallback={<Spinner />}>
<UsersPage />
</React.Suspense>
}
/>Result:
users bundle loads only when needed
initial load stays small
Routing boundaries are natural split points.
4. Avoid Over-Splitting
Too many small chunks can hurt performance.
Avoid:
lazy-loading tiny components
splitting frequently used UI
nesting Suspense everywhere
Rule of thumb:
split by features, not components
If users hit a route often, keep it eager.
5. Dynamic Imports for Heavy Dependencies
Some libraries are expensive but rarely used.
Example:
async function openEditor() {
const { Editor } = await import('./Editor');
setEditor(<Editor />);
}Good candidates:
rich text editors
charting libraries
PDF viewers
This keeps your main bundle lean.
6. Inspect Your Bundle (Non-Optional)
Add the visualizer:
pnpm add -D rollup-plugin-visualizerIn vite.config.ts:
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [visualizer()],
};After build, inspect:
large dependencies
duplicated code
unexpected bundles
Never optimize blindly.
7. Environment Variables: Be Explicit
Vite only exposes env vars prefixed with VITE_.
Example:
VITE_API_URL=https://api.example.comAccess:
const apiUrl = import.meta.env.VITE_API_URL;This prevents accidental leaks of secrets.
8. Separate Build-Time and Runtime Concerns
Environment variables are baked at build time.
That means:
changing envs requires a rebuild
frontend envs are not secrets
Never put:
API keys
database credentials
private tokens
Frontend envs are configuration, not security.
9. Environment Files per Stage
Typical setup:
.env
.env.development
.env.productionVite automatically loads based on mode.
Run builds explicitly:
pnpm build --mode productionPredictability beats cleverness.
10. Dead Code Is a Feature
Vite + TypeScript + ES modules allow dead code elimination.
Example:
if (import.meta.env.DEV) {
console.log('debug');
}This code is removed from production builds.
Use this intentionally for:
debug helpers
dev-only logging
local tooling
11. Asset Handling: Images and Fonts
Vite:
inlines small assets
hashes filenames
optimizes imports
Prefer imports over static paths:
import logo from './logo.svg';This ensures:
cache busting
correct URLs
predictable builds
12. Preload What Matters
Critical routes and assets should load early.
Vite handles module preloading automatically, but:
large hero images
above-the-fold assets
May benefit from manual attention.
Optimize only after measuring.
13. Build Failures Should Be Fast and Loud
Treat build warnings as signals.
Watch for:
circular dependencies
large bundle warnings
unused imports
Build-time feedback is cheaper than runtime bugs.
14. Summary
You now know how to:
reason about Vite’s build phase
split code by routes and features
lazy-load heavy dependencies
inspect and understand bundles
manage environment variables safely
avoid leaking secrets
keep production builds intentional
Related
Part 7.4 — Performance Hooks: useMemo, useCallback & useDeferredValue
Performance hooks are scalpels, not band-aids. This post teaches you how to use them intentionally—only where they solve real problems.
Part 7.3 — Custom Hooks as Architecture: Patterns & Pitfalls
Custom hooks aren’t just helpers. Used well, they define architectural seams in your React app. Used poorly, they hide complexity and make refactoring painful.
Part 7.2 — Advanced Routing Patterns: Protected Routes & URL State
Routing becomes powerful when URLs express intent. This post shows how to protect routes, manage auth flows, and encode UI state in the URL—without hacks.
Comments