Episode 2 — React Frontend Architecture NextJS / 2.1 — Introduction to React
2.1.e — Setting Up React With Vite
In one sentence: Vite is a next-generation build tool that provides instant dev server startup and lightning-fast Hot Module Replacement by leveraging native ES modules during development and Rollup for optimized production builds.
Navigation: ← Real DOM vs Virtual DOM · Next → Project Structure Breakdown
1. Why NOT Create React App (CRA) in 2024+
Create React App was the official React project scaffolding tool from 2016 to 2023. It was deprecated and is no longer recommended.
What Happened to CRA
CRA Timeline:
2016 — Released by Facebook. Revolutionary: zero-config React setup.
2017-2019 — The default way to start React projects. Loved.
2020 — Starts feeling slow. Alternative tools emerge (Vite, Snowpack).
2021 — Updates slow down. Community concerns grow.
2022 — Effectively unmaintained. Last significant update.
2023 — React team officially recommends frameworks (Next.js, Remix)
or Vite instead. CRA repository archived.
2024+ — Using CRA in a new project is actively discouraged.
Why CRA Became Problematic
CRA architecture:
Your code ─────> Webpack ─────> Bundle ─────> Browser
|
(config hidden via react-scripts)
|
SLOW. VERY SLOW.
| Problem | Detail |
|---|---|
| Slow startup | Dev server takes 30-60 seconds for large projects |
| Slow HMR | Changes take 2-10 seconds to reflect |
| No config access | Webpack config hidden inside react-scripts |
| Ejecting is permanent | npm run eject spills 1000+ lines of config |
| Heavy dependencies | node_modules bloat from Webpack ecosystem |
| Stale Webpack version | Stuck on Webpack 5 with slow updates |
| No SSR support | Cannot do server-side rendering |
| No code splitting defaults | Must configure manually |
| Security vulnerabilities | Unmaintained dependencies accumulate CVEs |
Dev server startup time comparison:
CRA (Webpack): ████████████████████████████████ 30-60s
Vite: ██ 1-2s
HMR (Hot Module Replacement):
CRA (Webpack): ████████████ 2-10s
Vite: █ <100ms
2. What Is Vite
Vite (French for "fast", pronounced /vit/) is a build tool created by Evan You (creator of Vue.js) in 2020. Despite its Vue origins, Vite is framework-agnostic and is the recommended tool for React projects.
Vite's Core Innovation
Vite uses two different strategies for development and production:
DEVELOPMENT: Native ES Modules (no bundling)
Browser requests main.jsx
|
v
Vite serves main.jsx as ES module
Browser parses imports:
import App from './App.jsx'
|
v
Browser requests App.jsx
Vite transforms it on-the-fly (JSX → JS)
Serves it immediately
|
v
Browser parses App's imports:
import Header from './components/Header.jsx'
import './App.css'
|
v
Browser requests each import individually
Vite transforms and serves each one
KEY INSIGHT: Only the files you actually import are processed.
If you have 500 files but only import 20 for the current page,
only 20 files are processed.
PRODUCTION: Rollup bundling (optimized output)
All source files ──> Rollup ──> Optimized bundles
|
+── index-abc123.js (main bundle)
+── vendor-def456.js (React, etc.)
+── about-ghi789.js (lazy chunk)
+── style-jkl012.css
How Vite's Dev Server Differs from Webpack
Webpack dev server (CRA):
Start dev server:
1. Read ALL source files
2. Build dependency graph (ALL files)
3. Transform ALL files (Babel, PostCSS, etc.)
4. Bundle EVERYTHING into memory
5. Serve bundle to browser
Time: 30-60 seconds (scales with project size)
On file change:
1. Determine what changed
2. Re-bundle affected modules
3. Send update to browser via WebSocket
Time: 2-10 seconds
Vite dev server:
Start dev server:
1. Pre-bundle node_modules (esbuild, ~1 second, cached)
2. Start HTTP server
3. Done.
Time: 1-2 seconds (constant, regardless of project size)
On file change:
1. Invalidate the changed module
2. Browser re-requests just that module
3. Vite transforms just that one file
Time: <100ms (constant, regardless of project size)
Why Vite Is Fast: esbuild
Vite uses esbuild for dependency pre-bundling. esbuild is written in Go (not JavaScript) and is 10-100x faster than JavaScript-based bundlers:
Bundling speed comparison (bundling React + dependencies):
Webpack (JavaScript): ████████████████████ ~20 seconds
Rollup (JavaScript): ████████████████ ~16 seconds
Parcel (JavaScript): ███████████ ~11 seconds
esbuild (Go): █ ~0.4 seconds
esbuild is 25-50x faster because:
- Written in Go (compiled, parallelized)
- Uses all CPU cores
- Minimal memory allocation
- Single-pass architecture
3. Installation Walkthrough
Prerequisites
You need Node.js installed (version 18+):
# Check Node.js version
node --version
# Should show v18.x.x or v20.x.x or higher
# Check npm version
npm --version
# Should show 9.x.x or higher
Creating a New React Project
# The command to create a new Vite project:
npm create vite@latest
# This runs the create-vite package
# It will prompt you with questions:
# ? Project name: > my-react-app
# ? Select a framework:
# Vanilla
# Vue
# > React
# Preact
# Lit
# Svelte
# Solid
# Qwik
# Angular
# Others
# ? Select a variant:
# > TypeScript
# TypeScript + SWC
# JavaScript
# JavaScript + SWC
# Remix ↗
You can also skip the prompts by specifying the template:
# JavaScript + React
npm create vite@latest my-react-app -- --template react
# TypeScript + React
npm create vite@latest my-react-app -- --template react-ts
# TypeScript + React + SWC (faster builds)
npm create vite@latest my-react-app -- --template react-swc-ts
# JavaScript + React + SWC
npm create vite@latest my-react-app -- --template react-swc
After Creation
# Navigate into the project
cd my-react-app
# Install dependencies
npm install
# Start the development server
npm run dev
# Output:
# VITE v5.x.x ready in 300 ms
#
# ➜ Local: http://localhost:5173/
# ➜ Network: use --host to expose
# ➜ press h + enter to show help
The dev server starts in ~300ms. Open http://localhost:5173 in your browser.
Step-by-Step: What Happens When You Run npm create vite@latest
1. npm downloads the `create-vite` package (temporarily)
2. create-vite runs its CLI
3. You answer the prompts (or flags provide answers)
4. create-vite copies template files into the project directory:
- package.json (with dependencies listed)
- vite.config.js (Vite configuration)
- index.html (the SPA entry point)
- src/ folder (application code)
- public/ folder (static assets)
5. Template files are written to disk
6. You run `npm install` to download dependencies
7. You run `npm run dev` to start the dev server
4. Available Templates
Vite offers several React templates. Each is a combination of language and compiler:
| Template | Language | JSX Compiler | Use Case |
|---|---|---|---|
react | JavaScript | Babel | Simple projects, learning |
react-ts | TypeScript | Babel | Production projects (recommended) |
react-swc | JavaScript | SWC | Performance-focused, Babel alternative |
react-swc-ts | TypeScript | SWC | Fastest TypeScript option |
Babel vs SWC
Babel (traditional):
- Written in JavaScript
- Huge plugin ecosystem
- Slower transformation
- More mature, more plugins available
SWC (modern):
- Written in Rust
- 20-70x faster than Babel
- Growing plugin ecosystem
- Used by Next.js, Deno, and others
- Recommended for new projects
Transformation speed:
Babel: ████████████████████ ~700ms for 1000 files
SWC: █ ~30ms for 1000 files
Recommendation
For new projects: react-swc-ts (TypeScript + SWC)
- TypeScript catches bugs at compile time
- SWC is faster than Babel
- The industry standard for production React projects
For learning and experimenting: react (JavaScript)
- No TypeScript complexity while learning React concepts
- Fewer files and configuration to understand
5. Understanding vite.config.js
The Vite configuration file controls how Vite behaves:
// vite.config.js (default for React template)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
That's the minimum. Here's a fully annotated version with common options:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
// PLUGINS
// Vite plugins extend functionality
plugins: [
react({
// Use automatic JSX runtime (no need to import React)
jsxRuntime: 'automatic',
// Babel plugins (only if using @vitejs/plugin-react, not SWC)
babel: {
plugins: [
// Add Babel plugins here if needed
],
},
}),
],
// DEV SERVER
server: {
port: 3000, // Default is 5173
open: true, // Open browser on start
host: true, // Listen on all network interfaces
strictPort: true, // Fail if port is in use (vs auto-incrementing)
// Proxy API requests to backend during development
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
// BUILD
build: {
outDir: 'dist', // Output directory (default: dist)
sourcemap: true, // Generate source maps for debugging
minify: 'esbuild', // Minifier: 'esbuild' (fast) or 'terser' (smaller)
target: 'es2015', // Browser compatibility target
rollupOptions: {
output: {
// Custom chunk splitting
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
// RESOLVE
resolve: {
alias: {
// Path aliases: import from '@/components/Button'
// instead of '../../components/Button'
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@hooks': path.resolve(__dirname, './src/hooks'),
'@utils': path.resolve(__dirname, './src/utils'),
},
},
// CSS
css: {
modules: {
localsConvention: 'camelCase', // .my-class → styles.myClass
},
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
},
// PREVIEW (production preview server)
preview: {
port: 4173,
open: true,
},
// DEFINE (compile-time constants)
define: {
__APP_VERSION__: JSON.stringify('1.0.0'),
},
});
Key Configuration Sections
vite.config.js structure:
plugins: Extensions that add functionality (React, PWA, etc.)
server: Dev server settings (port, proxy, CORS)
build: Production build settings (output, minification, chunks)
resolve: Module resolution (aliases, extensions)
css: CSS processing (modules, preprocessors)
preview: Production preview server settings
define: Compile-time constant replacements
optimizeDeps: Dependency pre-bundling configuration
6. Dev Server: npm run dev
When you run npm run dev, here's what happens under the hood:
$ npm run dev
[1] npm reads package.json scripts.dev → "vite"
[2] Vite starts
[3] PRE-BUNDLING (first run only, cached after):
Vite scans your code for bare imports (like 'react', 'react-dom')
Uses esbuild to bundle node_modules into optimized chunks
Stores them in node_modules/.vite/
Why? node_modules has thousands of files with CommonJS modules.
Browsers can't import CommonJS. Pre-bundling converts them to
ES modules and consolidates them (react has 30+ files → 1 file).
This runs once and is cached until dependencies change.
[4] HTTP SERVER starts on http://localhost:5173
[5] Browser requests http://localhost:5173/
Vite serves index.html:
<script type="module" src="/src/main.jsx"></script>
The type="module" tells the browser this is an ES module.
[6] Browser requests /src/main.jsx
Vite transforms it on-the-fly:
- JSX → JavaScript (via Babel or SWC)
- Import paths resolved
- Served with proper Content-Type header
[7] Browser parses main.jsx, finds imports:
import App from './App.jsx'
import './index.css'
Browser requests each import
[8] Process repeats for each imported file:
File requested → Vite transforms → Browser receives → parses imports
This is the "unbundled" development model.
Only imported files are processed. Unused files are ignored.
What the Browser Network Tab Shows
Network requests during Vite dev:
GET / → index.html
GET /src/main.jsx → transformed main.jsx (ES module)
GET /src/App.jsx → transformed App.jsx
GET /src/App.css → CSS (injected via JS)
GET /src/components/Header.jsx → transformed Header.jsx
GET /src/components/Footer.jsx → transformed Footer.jsx
GET /@react-refresh → Vite's HMR client
GET /node_modules/.vite/deps/react.js → pre-bundled React
GET /node_modules/.vite/deps/react-dom.js → pre-bundled ReactDOM
Each file is a separate HTTP request.
In production, these are bundled. In dev, they're individual.
This is fast because transforms are per-file (no bundling step).
7. Hot Module Replacement (HMR)
HMR is the ability to update modules in the browser while the application is running, without a full page reload. Vite's HMR is near-instant.
How HMR Works
You edit App.jsx and save:
[1] Filesystem watcher detects change to App.jsx
[2] Vite invalidates App.jsx in its module graph
[3] Vite re-transforms App.jsx (JSX → JS)
[4] Vite sends WebSocket message to browser:
{ type: 'update', path: '/src/App.jsx', timestamp: 1703... }
[5] Browser's HMR client receives the message
[6] Browser fetches the updated /src/App.jsx?t=1703... (cache-busted)
[7] React Fast Refresh:
- Preserves component STATE (useState values kept!)
- Re-renders only the changed component
- Does NOT remount the component (useEffect doesn't re-run)
[8] Updated UI visible in browser
Total time: ~20-100ms
You see the change almost before you look up from your editor.
What HMR Preserves vs Resets
HMR updates (component code changed):
PRESERVED:
- useState values (current state kept)
- useRef values
- DOM scroll position
- Input field values
- Focus state
RE-EXECUTED:
- Component render function (with current state)
- useMemo/useCallback (recomputed)
NOT RE-EXECUTED:
- useEffect (unless dependencies changed)
- Event handlers (swapped to new version)
FULL RELOAD triggered when:
- You edit a file that's not a React component
- You edit a file imported by many modules
- Syntax error in the file
- HMR handler throws an error
React Fast Refresh
React Fast Refresh is the React-specific HMR implementation. It's smarter than a generic HMR because it understands React component boundaries:
// You have this component:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
// User clicks + three times. Count is now 3.
// You edit the component to add color:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p style={{ color: 'blue' }}>Count: {count}</p> {/* Added color */}
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
// After HMR:
// Count is STILL 3 (state preserved!)
// But the text is now blue (new rendering applied)
// No page reload. No state loss.
8. Build Process: npm run build
For production, Vite uses Rollup to create optimized bundles:
$ npm run build
# Output:
# vite v5.x.x building for production...
# transforming...
# rendering chunks...
# computing gzip size...
#
# dist/index.html 0.46 kB │ gzip: 0.30 kB
# dist/assets/index-DiwrgTda.css 1.42 kB │ gzip: 0.74 kB
# dist/assets/index-CdTXrpJf.js 143.36 kB │ gzip: 46.12 kB
What Happens During Build
BUILD PROCESS:
[1] Vite reads all source files
[2] Rollup resolves the dependency graph (starting from index.html)
[3] Transformations:
- JSX → JavaScript (Babel or SWC)
- TypeScript → JavaScript (if using TS)
- CSS Modules → scoped CSS
- Asset imports → hashed file references
[4] Tree shaking:
- Remove unused exports
- Remove dead code
- Eliminate unused CSS (if configured)
[5] Code splitting:
- Dynamic imports → separate chunks
- Vendor code → vendor chunk
- Shared code → common chunk
[6] Minification:
- JavaScript minified (esbuild or terser)
- CSS minified
- HTML minified
[7] Asset processing:
- Images optimized (if plugins configured)
- Fonts inlined or hashed
- SVGs optimized
[8] Output:
- dist/index.html (entry point)
- dist/assets/*.js (JavaScript chunks with content hashes)
- dist/assets/*.css (CSS with content hashes)
- dist/assets/* (images, fonts, etc.)
Content Hashing
Vite adds content hashes to filenames for cache busting:
dist/
index.html
assets/
index-CdTXrpJf.js ← hash changes when content changes
index-DiwrgTda.css ← different hash
logo-BnfkW3kZ.svg ← image hash
vendor-X8dj3kfS.js ← vendor chunk hash
This enables aggressive caching: you can set Cache-Control: max-age=31536000 (1 year) on all assets in /assets/. When code changes, the filename changes, busting the cache automatically. index.html should have Cache-Control: no-cache so the browser always gets the latest entry point.
9. Preview: npm run preview
After building, you can preview the production build locally:
$ npm run preview
# Output:
# ➜ Local: http://localhost:4173/
# ➜ Network: use --host to expose
This serves the dist/ folder using a static file server. It is NOT for production hosting — it's for verifying that the production build works correctly before deploying.
Dev vs Preview vs Production:
npm run dev:
- Serves source files directly (unbundled)
- Transforms on-the-fly
- HMR enabled
- Not optimized (no minification, no tree shaking)
- For development only
npm run preview:
- Serves the built dist/ folder
- Same output as production
- No HMR
- Optimized and minified
- For testing the build locally
Production hosting (Vercel, Netlify, etc.):
- Serves the dist/ folder
- CDN distribution
- Proper caching headers
- HTTPS
10. Vite vs CRA vs Webpack: Comparison
| Feature | Vite | CRA (deprecated) | Webpack (manual) |
|---|---|---|---|
| Dev startup | ~300ms | ~30-60s | ~10-30s |
| HMR speed | <100ms | 2-10s | 1-5s |
| Build tool | Rollup | Webpack | Webpack |
| Config | vite.config.js (simple) | Hidden (eject to access) | webpack.config.js (complex) |
| TypeScript | Built-in | Built-in | Manual setup |
| CSS Modules | Built-in | Built-in | Manual setup |
| Code splitting | Automatic (dynamic import) | Manual | Manual |
| Tree shaking | Rollup (excellent) | Webpack (good) | Webpack (good) |
| Proxy | Built-in | Built-in | Manual |
| SSR support | Yes | No | Manual |
| Library mode | Yes | No | Yes |
| Maintained | Actively | Deprecated | Actively |
| Community | Growing fast | Declining | Stable |
When to Use What
- Vite: New React projects (recommended default)
- Next.js: When you need SSR, SSG, or API routes
- Webpack: Legacy projects or highly custom build requirements
- CRA: Never for new projects. Migrate existing CRA projects to Vite.
Migrating from CRA to Vite
# High-level migration steps:
# 1. Remove CRA dependencies
npm uninstall react-scripts
# 2. Install Vite
npm install --save-dev vite @vitejs/plugin-react
# 3. Create vite.config.js
# (see configuration section above)
# 4. Move index.html from public/ to project root
# Change <script> tag to type="module" src="/src/index.jsx"
# 5. Update package.json scripts:
# "dev": "vite",
# "build": "vite build",
# "preview": "vite preview"
# 6. Rename environment variables:
# REACT_APP_* → VITE_*
# 7. Update import.meta.env references:
# process.env.REACT_APP_API_URL → import.meta.env.VITE_API_URL
# 8. Test everything
npm run dev
npm run build
11. Vite Plugin Ecosystem
Vite's plugin system is compatible with Rollup plugins and has its own plugin API.
Essential Plugins
// @vitejs/plugin-react — Required for React
// Already included in React templates
import react from '@vitejs/plugin-react';
// @vitejs/plugin-react-swc — SWC alternative (faster)
import react from '@vitejs/plugin-react-swc';
Commonly Used Plugins
// vite-plugin-svgr — Import SVGs as React components
import svgr from 'vite-plugin-svgr';
// Usage:
// import { ReactComponent as Logo } from './logo.svg';
// <Logo width={100} height={100} />
// vite-plugin-checker — TypeScript and ESLint checking
import checker from 'vite-plugin-checker';
export default defineConfig({
plugins: [
react(),
svgr(),
checker({
typescript: true,
eslint: {
lintCommand: 'eslint "./src/**/*.{ts,tsx}"',
},
}),
],
});
// vite-plugin-pwa — Progressive Web App support
import { VitePWA } from 'vite-plugin-pwa';
// @tanstack/router-vite-plugin — TanStack Router file-based routing
import { TanStackRouterVite } from '@tanstack/router-vite-plugin';
// vite-plugin-compression — Gzip/Brotli compression
import compression from 'vite-plugin-compression';
// vite-plugin-inspect — Inspect intermediate transforms
import inspect from 'vite-plugin-inspect';
Plugin Configuration Example
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import svgr from 'vite-plugin-svgr';
import checker from 'vite-plugin-checker';
import path from 'path';
export default defineConfig({
plugins: [
react(),
svgr({
svgrOptions: {
icon: true,
},
}),
checker({
typescript: true,
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
12. Environment Variables in Vite
Vite uses .env files for environment-specific configuration, but with a key difference from CRA: variables must be prefixed with VITE_.
.env Files
# .env — loaded in all environments
VITE_APP_TITLE=My React App
# .env.local — loaded in all environments, git-ignored
VITE_API_KEY=your-secret-key-here
# .env.development — loaded only during `npm run dev`
VITE_API_URL=http://localhost:8080/api
# .env.production — loaded only during `npm run build`
VITE_API_URL=https://api.production.com
# .env.development.local — dev + git-ignored
# .env.production.local — prod + git-ignored
Loading Priority
Priority (highest to lowest):
.env.{mode}.local (e.g., .env.development.local)
.env.{mode} (e.g., .env.development)
.env.local
.env
"mode" is 'development' for `vite` (dev server)
"mode" is 'production' for `vite build`
Accessing Environment Variables
// In your React code:
const apiUrl = import.meta.env.VITE_API_URL;
const appTitle = import.meta.env.VITE_APP_TITLE;
// Built-in variables:
import.meta.env.MODE // 'development' or 'production'
import.meta.env.BASE_URL // Base URL from vite.config.js
import.meta.env.PROD // true in production
import.meta.env.DEV // true in development
import.meta.env.SSR // true during SSR
// IMPORTANT: Only VITE_ prefixed variables are exposed to client code!
// This prevents accidentally leaking server secrets.
// This WILL NOT work in client code:
// DATABASE_URL=postgres://... (no VITE_ prefix → not exposed)
// SECRET_KEY=abc123 (no VITE_ prefix → not exposed)
TypeScript Support for Env Variables
// src/vite-env.d.ts (or env.d.ts)
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
readonly VITE_GOOGLE_ANALYTICS_ID: string;
// Add more as needed
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
This gives you autocomplete and type checking for environment variables.
13. TypeScript Setup With Vite
If you used the react-ts or react-swc-ts template, TypeScript is pre-configured. Here's what's set up:
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
Key settings explained:
| Setting | Purpose |
|---|---|
target: "ES2020" | Output ES2020 JavaScript (modern browsers) |
jsx: "react-jsx" | Use the automatic JSX runtime (no import React needed) |
strict: true | Enable all strict type checks |
moduleResolution: "bundler" | Resolve modules like Vite does |
noEmit: true | TypeScript only checks types; Vite handles compilation |
isolatedModules: true | Required — each file must be transformable independently |
Important: Vite Does NOT Type-Check
Vite's role: Transform TypeScript → JavaScript (fast, using esbuild)
TypeScript's role: Type checking (separate step)
Vite strips types but does NOT check them.
A type error will NOT prevent Vite from running.
To get type checking:
Option 1: IDE (VS Code) — real-time feedback
Option 2: npm run type-check → "tsc --noEmit" (CI/CD)
Option 3: vite-plugin-checker (shows errors in browser overlay)
Add a type-check script to package.json:
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"type-check": "tsc --noEmit"
}
}
The build script now runs TypeScript checking before building. If there are type errors, the build fails.
14. Path Aliases Configuration
Path aliases replace long relative imports with clean, absolute-style imports:
// Without aliases (painful in deep directories):
import Button from '../../../components/ui/Button';
import { useAuth } from '../../../hooks/useAuth';
import { formatDate } from '../../../utils/formatDate';
// With aliases (clean):
import Button from '@/components/ui/Button';
import { useAuth } from '@/hooks/useAuth';
import { formatDate } from '@/utils/formatDate';
Setting Up Path Aliases
Step 1: vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
Step 2: tsconfig.json (if using TypeScript)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Both files need the alias for it to work: Vite uses vite.config.js to resolve imports at build time, and TypeScript uses tsconfig.json for type checking and IDE autocomplete.
15. Common Vite Configuration Patterns
Pattern 1: API Proxy
During development, your React app runs on port 5173, but your API runs on port 8080. The proxy forwards API requests:
// vite.config.js
export default defineConfig({
server: {
proxy: {
// /api/users → http://localhost:8080/api/users
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
});
// In your React code, just use relative paths:
fetch('/api/users')
// In dev: proxied to http://localhost:8080/api/users
// In prod: goes to your actual API server
Pattern 2: Global CSS Variables / SCSS Setup
// vite.config.js
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
// This SCSS is prepended to every .scss file
additionalData: `
@use "@/styles/variables" as *;
@use "@/styles/mixins" as *;
`,
},
},
},
});
Pattern 3: Custom Chunk Splitting
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
// Group all node_modules into a 'vendor' chunk
if (id.includes('node_modules')) {
// Further split large libraries into their own chunks
if (id.includes('react')) return 'react-vendor';
if (id.includes('@tanstack')) return 'tanstack-vendor';
return 'vendor';
}
},
},
},
},
});
Pattern 4: Multiple Entry Points
// vite.config.js — for apps with multiple HTML pages
export default defineConfig({
build: {
rollupOptions: {
input: {
main: 'index.html',
admin: 'admin.html',
},
},
},
});
16. Key Takeaways
-
CRA is deprecated. Use Vite. Vite provides 10-100x faster development experience with better defaults and active maintenance.
-
Vite uses native ES modules in development. Instead of bundling your entire app before serving it, Vite serves individual files on demand. Only imported files are processed.
-
Vite uses Rollup for production builds. The production output is optimized, minified, tree-shaken, and code-split — ready for deployment.
-
esbuild makes pre-bundling fast. Dependencies from node_modules are pre-bundled once using esbuild (Go-based, 10-100x faster than JavaScript bundlers).
-
HMR is near-instant. When you save a file, the change appears in the browser in <100ms with component state preserved, thanks to React Fast Refresh.
-
Environment variables must use the
VITE_prefix to be exposed to client code. This prevents accidentally leaking server secrets. -
Vite does not type-check TypeScript. It strips types for speed. Use
tsc --noEmitseparately (in build scripts or CI) for type checking. -
Path aliases require configuration in two places —
vite.config.jsfor Vite's module resolution andtsconfig.jsonfor TypeScript and IDE support. -
The
react-swc-tstemplate is the recommended starting point for production React projects — TypeScript for safety, SWC for speed. -
npm run devfor development,npm run buildfor production,npm run previewto test the build locally. These three commands cover the entire development workflow.
Explain-It Challenge
-
Explain why Vite's dev server starts in ~300ms regardless of project size, while CRA's dev server takes 30-60 seconds and gets slower as the project grows. What architectural difference causes this?
-
A teammate is confused: "I added
DATABASE_URLto my.envfile butimport.meta.env.DATABASE_URLis undefined in my React code." Explain why this happens and what the correct approach is. -
Describe the complete journey of a single
.jsxfile from your editor to the browser screen during development with Vite. Include: file save, Vite's processing, browser request, and rendering.
Navigation: ← Real DOM vs Virtual DOM · Next → Project Structure Breakdown