Vite is a commonly used development server and build tool for modern front-end projects.
For the React + TypeScript + Vite project, it is mainly responsible for:
- development stage: Starts the development server, providing fast module loading and hot module replacement (HMR).
- production build stage: Convert, optimize and package TypeScript, JSX, CSS and other source codes into static resources that the browser can load.
Overall process:
React / TypeScript Source
│
▼
Vite
│
┌─────┴─────┐
▼ ▼
Development Production
│ │
Dev Server Build
│ │
▼ ▼
HMR / ESM dist/
│
▼
Nginx / CDN
│
▼
Browser
As with plain React SPA, Vite does not need to be continuously running on the production server. The constructed HTML, JavaScript, CSS and other resources can be directly provided by Nginx.
1. Development server (Dev Server)
Start command
pnpm dev
Usually corresponds to package.json:
{
"scripts": {
"dev": "vite"
}
}
The default development address is usually:
http://localhost:5173
Common commands:
# Start the development server
pnpm dev
# Specify port
pnpm dev -- --port 3000
# Allow access outside LAN or container
pnpm dev -- --host 0.0.0.0
# Force pre-build dependencies to be rebuilt
pnpm dev -- --force
-- in pnpm dev -- represents the program that passes subsequent parameters to the script for execution.
How the development server works
Traditional Bundler usually requires building some or all modules before providing development services.
Vite uses the browser's native ES Modules (ESM) to convert and provide source code modules on demand.
Browser
│
├── GET /src/main.tsx
│
├── GET /src/App.tsx
│
└── GET /src/components/Header.tsx
│
▼
Vite Dev Server
│
▼
Transform / Serve
This reduces the amount of work that must be done when a large project is started.
However, Vite will still perform module conversion and dependency pre-building during the development phase, and does not perform any building processing at all.
2. HMR(Hot Module Replacement)
HMR is one of the core mechanics in the Vite development experience.
When the source code changes:
Modify React Component
↓
Vite Detects Changes
↓
HMR Update
↓
Browser Replaces Module
↓
Page Updates
HMR It is possible to update only the changed modules instead of reloading the entire page.
React Fast Refresh
React projects typically use:
import react from "@vitejs/plugin-react";
React Fast Refresh is based on HMR, which updates components when conditions are met, preserving React State when possible.
For example, modify:
export function Counter() {
const [count, setCount] = useState(0);
return <button>{count}</button>;
}
After the component code is modified, the state of the counter can be preserved in some cases.
Note:
- HMR is a universal module hot replacement mechanism.
- Fast Refresh is a hot update mechanism specifically for React components.
- Fast Refresh There is no guarantee that component status will be preserved with every modification.
3. Dependency pre-build (Dependency Pre-Bundling)
Vite The development stage usually preprocesses some third-party dependencies.
For example:
import React from "react";
import axios from "axios";
These dependencies may involve:
- CommonJS to ESM.
- Lots of internal modules.
- Multi-level dependency references.
Vite will use the dependency optimization mechanism to convert and merge suitable pre-built dependencies in advance to improve the loading efficiency during the development phase.
The cache is usually located at:
node_modules/.vite/
optimizeDeps
Can be configured:
export default defineConfig({
optimizeDeps: {
include: ["react", "react-dom"],
exclude: [],
},
});
| Configuration | function |
|---|---|
include | Explicitly specify dependencies that need to be prebuilt |
exclude | Exclude pre-builds of certain dependencies |
force | Force dependencies to be re-optimized |
There is usually no need to manually modify optimizeDeps.
It is only worth adjusting if you encounter slow loading of specific dependencies, CommonJS compatibility issues, or abnormal dependency discovery.
relies on pre-construction, which is mainly an optimization mechanism for the development environment and is not equivalent to Code Splitting for the production environment.
4. Production build (Production Build)
Run:
pnpm build
Usually corresponds to:
{
"scripts": {
"build": "tsc -b && vite build"
}
}
Execution process:
TypeScript Type Check
↓
Vite Build
↓
Module Bundling
↓
Tree Shaking
↓
Minification
↓
dist/
Note: vite build itself is mainly responsible for the construction and does not perform the full semantic type checking of TypeScript like tsc --noEmit.
So the React + TypeScript project will usually run alone:
tsc -b
Build product
Typically generates:
dist/
├── index.html
└── assets/
├── index-A1b2C3.js
├── index-D4e5F6.css
├── MapPage-K7m8N9.js
└── logo-P1q2R3.svg
These files are static resources deployed to Nginx or CDN.
build configuration
export default defineConfig({
build: {
outDir: "dist",
sourcemap: false,
emptyOutDir: true,
chunkSizeWarningLimit: 500,
},
});
| Configuration | function |
|---|---|
outDir | Build result output directory |
sourcemap | Whether to generate Source Map |
emptyOutDir | Whether to clean the output directory before building |
chunkSizeWarningLimit | Chunk size warning threshold |
target | Build the target browser environment |
minify | Whether and how to compress code |
chunkSizeWarningLimit only controls the warning threshold and does not actually reduce the Bundle size.
5. Preview: Preview production build
pnpm build
pnpm preview
Usually corresponds to:
{
"scripts": {
"preview": "vite preview"
}
}
Function:
dist/
↓
Vite Preview Server
↓
Browser
Can be used to check:
- Whether the page after construction is normal.
- Is the dynamic import correct?
- Whether the static resource path is valid.
- Whether the page route is accessible.
vite preview is mainly used for local preview and is not recommended as an official production server.
6. vite.config.ts
The main configuration file for Vite is usually:
vite.config.ts
React + TypeScript + Tailwind CSS project example:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
host: "127.0.0.1",
port: 5173,
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
},
},
build: {
outDir: "dist",
sourcemap: false,
},
resolve: {
tsconfigPaths: true,
},
base: "/",
});
resolve.tsconfigPaths here applies to newer versions of Vite that support this option; older versions can continue to use resolve.alias.
Common configuration categories
| Configuration | function |
|---|---|
plugins | Load plugin |
server | Development server configuration |
server.proxy | Development environment reverse proxy |
build | Production build |
resolve | Module path resolution |
base | Resource public path |
define | Define compile-time constants |
css | CSS processing |
preview | Preview server configuration |
optimizeDeps | Development dependency optimization |
7. Path alias (Alias)
Assuming code structure:
src/
├── components/
│ └── Button.tsx
├── pages/
└── main.tsx
If Alias is not used:
import { Button } from "../../components/Button";
Use Alias:
import { Button } from "@/components/Button";
TypeScript configuration
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Vite configuration
Method 1: Use resolve.alias.
import { fileURLToPath, URL } from "node:url";
export default defineConfig({
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
Method 2: The newer version Vite can be used:
resolve: {
tsconfigPaths: true,
}
Causes Vite to resolve the path alias based on the matching tsconfig.json.
Note: This feature may bring some parsing overhead and cannot solve all path parsing problems in CSS or third-party tools.
8. Development proxy (server.proxy)
During development, React and Django may run on different ports:
React → localhost:5173
Django → localhost:8000
Can be configured:
server: {
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
},
},
Browser request:
GET http://localhost:5173/api/v1/pois/
Vite forwards requests to:
GET http://localhost:8000/api/v1/pois/
This way front-end code can use relative addresses:
axios.get("/api/v1/pois/");
There is no need to request another Origin directly in the development environment.
production environment
server.proxy is only used on the Vite development server.
Nginx is usually responsible for production:
Browser
↓
Nginx
├── /assets/ → Static Files
├── /api/ → Django
└── / → React SPA
9. Environment variables (Environment Variables)
Vite uses .env files to manage configurations for different environments.
Common documents:
.env
.env.local
.env.development
.env.production
.env.staging
Example
.env.development:
VITE_API_URL=http://localhost:8000
.env.production:
VITE_API_URL=https://api.example.com
The code reads:
const apiUrl = import.meta.env.VITE_API_URL;
Built-in environment variables
| variable | function |
|---|---|
import.meta.env.MODE | Current Vite Mode |
import.meta.env.DEV | Whether it is development mode |
import.meta.env.PROD | Whether it is production mode |
import.meta.env.BASE_URL | Resource base path |
import.meta.env.SSR | Whether you are in the SSR build environment |
For example:
if (import.meta.env.DEV) {
console.log("Development");
}
Some development-specific code can be removed during production builds via static substitution and Tree Shaking.
Environment variable security
By default, only custom environment variables with the VITE_ prefix are exposed to client code.
But special attention is needed:
All environment variables exposed to the browser should be considered public information.
Cannot put:
- Database password.
- Django SECRET_KEY。
- Private API Token.
- Server access credentials.
Build-time and Runtime
The client environment variable for Vite is usually determined at build time.
.env.production
↓
vite build
↓
JavaScript Bundle
↓
Nginx
If you modify .env.production on the server after building, the already generated JS file will not be changed automatically.
If runtime dynamic configuration is required, additional configuration files or configuration interface mechanisms are required.
10. Static resource processing
Vite There are two main methods of static resource management.
src/assets
src/assets/logo.png
Use:
import logo from "@/assets/logo.png";
function Logo() {
return <img src={logo} alt="Logo" />;
}
Vite Processes resource paths when building and may inline or generate hashed files based on resource content, size, and configuration.
For example:
/assets/logo-Cx7Ab91.png
public
public/favicon.ico
Usually copied unchanged to:
dist/favicon.ico
Use:
<img src="/favicon.ico" alt="Favicon" />
The difference between the two
| Features | src/assets | public |
|---|---|---|
| Processed by building resources | Yes | Usually no |
| Automatic Hash | Usually supported | Do not add automatically |
| Can be referenced via import | Yes | Usually not recommended |
| Fixed URL | Not necessarily | Yes |
| Applicable scenarios | Application resources | favicon, fixed path file |
Common application pictures, fonts and other resources are usually placed first in src/assets.
Resources that need to keep a fixed file name or fixed URL can be placed at public.
11. Code Splitting (code splitting)
Code Splitting is used to split JavaScript into multiple Chunk to avoid users having to download the entire application code when they first visit.
Dynamic Import
import { lazy, Suspense } from "react";
const MapPage = lazy(() => import("./pages/MapPage"));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MapPage />
</Suspense>
);
}
Vite can split dynamically imported modules into independent Chunk during production builds.
For example:
dist/assets/
├── index-A1b2.js
├── MapPage-C3d4.js
└── maplibre-E5f6.js
Actual splitting results depend on module dependencies and build configuration.
Lazy Loading and Code Splitting
The two are related, but not identical.
- Code Splitting: Split the code into multiple files that can be loaded independently.
- Lazy Loading: Defer loading code or resources until needed.
Typical process:
User Opens Homepage
↓
main.js
↓
User Opens Map Page
↓
MapPage Chunk
↓
MapLibre Chunk
If MapPage is rendered immediately on the homepage, then even if lazy() is used, the related module may still be loaded on the homepage.
True page-level lazy loading needs to be coordinated with routing or conditional rendering.
12. Tree Shaking
Tree Shaking is used to remove code determined to be unused during the build process.
For example:
export function a() {}
export function b() {}
export function c() {}
Just quote:
import { a } from "./utils";
Vite can remove unused b() and c() in production builds when static analysis and side effect handling conditions are met.
limitations
Tree Shaking may be affected by the following factors:
- CommonJS。
- Dynamic references.
- Module side effects.
- Third parties rely on packaging methods.
- The builder cannot prove that the code is not used.
Therefore, using ES Modules helps build tools perform static analysis, but does not guarantee that all unused code will be removed.
13. Minification (code compression)
Production builds typically compress JavaScript and CSS.
For example source code:
function calculateTotal(price: number, quantity: number) {
return price * quantity;
}
After compression it may become:
function t(n, r) {
return n * r;
}
Minification mainly includes:
- Remove unnecessary whitespace and comments.
- Shorten local identifiers.
- Simplify the expression.
- Remove code that proves to be useless.
Minification and gzip/Brotli
They solve different problems:
| technology | action stage | core role |
|---|---|---|
| Minification | Vite build | Reduce the size of the code itself |
| gzip | HTTP transmission | Compress data for network transmission |
| Brotli | HTTP transmission | Typically provides higher compression ratios |
| Lazy Loading | Browser loading | Defer unnecessary code downloads |
Typical process:
Source Code
↓
Vite Minification
↓
Minified JavaScript
↓
Nginx Brotli / gzip
↓
Network Transfer
↓
Browser Decompression
↓
Parse / Compile / Execute
gzip/Brotli does not reduce the amount of JavaScript code that the browser needs to process after decompression.
14. Bundle analysis and optimization
The build product can be analyzed when the dependent volume is large, such as MapLibre.
Common optimization methods
- Use page level Lazy Loading.
- Avoid introducing large dependencies directly in the entry file.
- Use Dynamic Import.
- Utilize Tree Shaking.
- Reasonably split Chunk.
- Enable HTTP compression and caching.
manualChunks
Some Vite versions and underlying builders support manual subcontracting configuration, for example:
build: {
rollupOptions: {
output: {
manualChunks: {
map: ["maplibre-gl"],
},
},
},
},
Note: This is an example based on the legacy Rollup configuration. The underlying builder and corresponding configuration of the newer version Vite API may be different, and version compatibility should be confirmed when using it.
Moreover, manual Chunk splitting may not necessarily reduce the total code size, nor does it guarantee that the related Chunk will not be loaded on the first screen.
It is generally recommended to use dynamic import and the default code splitting strategy first, and only manually adjust Chunk after analyzing Bundle.
Build volume warning
Some chunks are larger than 500 kB
This is a reminder from the build tool that some Chunk values are larger. It does not mean that the build failed, nor does it mean that the page must have serious performance problems.
Need to also consider:
- The size of the network after compression.
- Whether it belongs to the first screen resource.
- Whether to cache for a long time.
- JavaScript parsing and execution overhead.
- Actual device performance.
15. Source Map
Production code often goes through Minification, so the error stack may not be easy to read.
For example:
function a(e) {
return e + 1;
}
Source Map can help map the built code locations back to the original source code.
Production Error
↓
Minified JS Position
↓
Source Map
↓
src/components/Map.tsx:32
Configuration:
build: {
sourcemap: true,
},
Common options
| Configuration | function |
|---|---|
false | Not generated |
true | Generate independent Source Map |
"inline" | Embed in build file |
"hidden" | Generate standalone Source Map without adding Source Mapping URL annotation |
For error monitoring services such as Sentry, consider using hidden and uploading Source Map.
Note: Source Map may expose the source code structure, so it may not be suitable for direct public deployment.
16. Base: Deployment base path
base is used to configure the resource base path when the application is publicly accessible.
Deploy in root path
https://example.com/
Configuration:
base: "/",
Deploy in subpath
https://example.com/my-app/
Configuration:
base: "/my-app/",
Vite is built with adjustments to base:
- JS resource reference.
- CSS resource reference.
- Static resource path.
- Related resource addresses in HTML.
Note: base does not automatically configure React Router.
If React Router uses Browser History and is deployed under a subpath, basename of Router also needs to be configured correctly.
17. Plugin: Vite plug-in system
Vite extends functionality through plug-ins.
Common plug-ins:
| Plugin | function |
|---|---|
@vitejs/plugin-react | React supports and Fast Refresh |
@tailwindcss/vite | Tailwind CSS integrated |
vite-plugin-svgr | SVG as React component |
vite-plugin-pwa | PWA support |
| Bundle Analyzer class plug-in | Analyze build products |
| TypeScript/ESLint check plug-in | Assisted development checks |
For example:
export default defineConfig({
plugins: [react(), tailwindcss()],
});
The plug-in mechanism of Vite is compatible with the Rollup plug-in system, but Vite also provides proprietary capabilities for development servers and module conversion.
18. Vite and Nginx production deployment
Vite is responsible for building static resources, and Nginx is responsible for the HTTP service in the production environment.
React Source
↓
Vite Build
↓
dist/
↓
Docker Image
↓
Nginx
↓
Browser
Docker multi-stage build
# Stage 1: Build
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
In this way, the final running image does not need to contain the Node.js development dependency.
Nginx SPA configuration
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
}
location = /index.html {
add_header Cache-Control "no-cache";
}
}
It is assumed here that Vite uses /assets/ by default to output static resources with hashes.
Why can't index.html be cached long-term?
Vite build files typically contain content hashes:
index-A1b2C3.js
After code update:
index-X9y8Z7.js
So hashed files can be cached for a long time.
But index.html must be able to discover the latest build in a timely manner, since it is responsible for referencing the new JS/CSS files.
Recommended:
index.html
↓
no-cache(Re-verify before use)
assets/*.js
assets/*.css
↓
Long-term Cache
Note: For SPA, you should also ensure that non-existing static resources do not fall back to index.html by mistake, otherwise a JavaScript MIME Type error may occur.
19. Vite, Nginx and browser responsibilities
| Technology | Main responsibilities |
|---|---|
| React | UI component and application status |
| TypeScript | static type checking |
| Vite Dev Server | Development Services and HMR |
| Vite Build | Module packaging, code conversion and optimization |
| Nginx | HTTP service, reverse proxy, compression, cache |
| Browser | Download, cache, parse and execute resources |
Typical production optimization:
React.lazy()
↓
Dynamic Import
↓
Vite Code Splitting
↓
Hashed Static Assets
↓
Nginx gzip / Brotli
↓
Cache-Control
↓
Browser
This is a common production deployment method for React SPA.
20. Vite FAQ
Q1: Is Vite Web Framework?
no.
Vite is mainly a development server and build tools.
React is responsible for UI; React Router is responsible for client routing; Vite is responsible for development and construction.
Q2: Does the Vite production environment need to run Node.js?
For ordinary static React SPA, not required.
The static resources generated by the Vite build can be provided directly by Nginx or CDN.
However, server-side rendering scenarios such as SSR may require Node.js or other server runtimes.
Q3: Will Vite automatically turn on gzip?
Production servers are not automatically enabled for gzip.
The gzip volume shown in the build output is usually just compressed volume information and does not represent Nginx with gzip enabled.
Q4: Can Vite be automatically followed by Code Splitting?
Can.
The production builder of Vite will generate Chunk based on information such as module relationships and dynamic imports.
However, the actual split results are affected by builders, plugins and configurations.
Q5: What is the difference between Vite and Webpack?
Both can complete front-end builds.
The main features of Vite are leveraging native ESM to provide rapid development services and using production builders to optimize output resources.
Webpack provides a highly configurable module packaging mechanism.
The actual performance of the two depends on the project size, configuration and version used. One tool cannot simply be considered faster in all situations.
Summary
The core knowledge of Vite can be divided into four parts:
| Classification | focus |
|---|---|
| development mechanism | Dev Server、ESM、HMR、Fast Refresh、Dependency Pre-Bundling |
| Build optimization | Build、Code Splitting、Tree Shaking、Minification |
| Project configuration | vite.config.ts、Alias、Proxy、Env、Base、Plugin |
| Production deployment | dist、Hashed Assets、Source Map、Nginx、Cache-Control |
For the React + TypeScript SPA project, the most important things to master are:
- How the development server works with HMR.
- Common configuration of
vite.config.ts. - The difference between development proxies and production reverse proxies.
- Build-time injection mechanism for environment variables.
- Dynamic Import and Code Splitting.
- Static resources Hash and browser cache.
- The difference between Vite Build and TypeScript type checking.