← ProgrammingBack to top

Vite: Front-end development server and build tools

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:

  1. development stage: Starts the development server, providing fast module loading and hot module replacement (HMR).
  2. 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: [],
  },
});
Configurationfunction
includeExplicitly specify dependencies that need to be prebuilt
excludeExclude pre-builds of certain dependencies
forceForce 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,
  },
});
Configurationfunction
outDirBuild result output directory
sourcemapWhether to generate Source Map
emptyOutDirWhether to clean the output directory before building
chunkSizeWarningLimitChunk size warning threshold
targetBuild the target browser environment
minifyWhether 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

Configurationfunction
pluginsLoad plugin
serverDevelopment server configuration
server.proxyDevelopment environment reverse proxy
buildProduction build
resolveModule path resolution
baseResource public path
defineDefine compile-time constants
cssCSS processing
previewPreview server configuration
optimizeDepsDevelopment 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

variablefunction
import.meta.env.MODECurrent Vite Mode
import.meta.env.DEVWhether it is development mode
import.meta.env.PRODWhether it is production mode
import.meta.env.BASE_URLResource base path
import.meta.env.SSRWhether 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

Featuressrc/assetspublic
Processed by building resourcesYesUsually no
Automatic HashUsually supportedDo not add automatically
Can be referenced via importYesUsually not recommended
Fixed URLNot necessarilyYes
Applicable scenariosApplication resourcesfavicon, 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:

technologyaction stagecore role
MinificationVite buildReduce the size of the code itself
gzipHTTP transmissionCompress data for network transmission
BrotliHTTP transmissionTypically provides higher compression ratios
Lazy LoadingBrowser loadingDefer 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

  1. Use page level Lazy Loading.
  2. Avoid introducing large dependencies directly in the entry file.
  3. Use Dynamic Import.
  4. Utilize Tree Shaking.
  5. Reasonably split Chunk.
  6. 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

Configurationfunction
falseNot generated
trueGenerate 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:

Pluginfunction
@vitejs/plugin-reactReact supports and Fast Refresh
@tailwindcss/viteTailwind CSS integrated
vite-plugin-svgrSVG as React component
vite-plugin-pwaPWA support
Bundle Analyzer class plug-inAnalyze build products
TypeScript/ESLint check plug-inAssisted 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

TechnologyMain responsibilities
ReactUI component and application status
TypeScriptstatic type checking
Vite Dev ServerDevelopment Services and HMR
Vite BuildModule packaging, code conversion and optimization
NginxHTTP service, reverse proxy, compression, cache
BrowserDownload, 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:

Classificationfocus
development mechanismDev Server、ESM、HMR、Fast Refresh、Dependency Pre-Bundling
Build optimizationBuild、Code Splitting、Tree Shaking、Minification
Project configurationvite.config.ts、Alias、Proxy、Env、Base、Plugin
Production deploymentdist、Hashed Assets、Source Map、Nginx、Cache-Control

For the React + TypeScript SPA project, the most important things to master are:

  1. How the development server works with HMR.
  2. Common configuration of vite.config.ts.
  3. The difference between development proxies and production reverse proxies.
  4. Build-time injection mechanism for environment variables.
  5. Dynamic Import and Code Splitting.
  6. Static resources Hash and browser cache.
  7. The difference between Vite Build and TypeScript type checking.

Reference documentation