Deploy Vite App to VPS: React, Vue, Svelte Guide 2026
Master the complete process of deploying Vite-built applications (React, Vue, Svelte) to a VPS with Nginx static hosting, proper SPA routing, gzip compression, and production-grade caching. This guide covers everything from build optimization to common mistakes and automating with DeployWise.
Table of Contents
- Understanding Vite Build Output (dist/ Directory)
- Building for Production (npm run build)
- Nginx Configuration for Static Sites
- SPA Routing with try_files
- Gzip Compression and Performance
- Cache Headers for Assets
- Custom Base Path Configuration
- Environment Variables in Vite
- Common Mistakes and How to Avoid Them
- Automating with DeployWise
Understanding Vite Build Output (dist/ Directory)
When you build a Vite application, it generates optimized static files in the dist/ directory. Understanding this output structure is crucial for proper VPS deployment.
Typical Vite Build Output
dist/ ├── index.html # Entry HTML file (always served for SPA) ├── assets/ # Hashed asset files (CSS, JS, images) │ ├── main-a1b2c3d4.js # Main JavaScript bundle (hashed) │ ├── main-e5f6g7h8.css # CSS bundle (hashed) │ ├── vendor-i9j0k1l2.js # Vendor bundle (hashed) │ └── logo-m3n4o5p6.png # Images (hashed) ├── favicon.ico # Favicon └── robots.txt # SEO robots file
The key characteristics of Vite's build output:
- • Hashed filenames - Assets like
main-a1b2c3d4.jsinclude content hashes for cache busting - • Single index.html - All routes served through this file for SPAs
- • assets/ directory - Contains all bundled and optimized JavaScript and CSS
- • Zero runtime dependencies - Vite outputs pure static files, no server needed
The hashed filenames enable aggressive caching - old versions never conflict with new ones.
Building for Production (npm run build)
Before deploying to your VPS, build your Vite application for production. This command optimizes your code, minifies assets, and generates the deployment-ready dist/ directory.
Running the Build
npm run build
This command reads your Vite config and generates optimized static files. For most projects, this is all you need.
Typical vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
assetsDir: 'assets',
minify: 'terser',
sourcemap: false,
},
})Key Build Options
- outDir: Output directory name, default
'dist' - assetsDir: Subdirectory for assets, default
'assets' - minify: Minification method -
'terser'(default),'esbuild', orfalse - sourcemap: Generate source maps for debugging. Set to
falsefor production - rollupOptions: Advanced Rollup configuration for code splitting and optimization
Previewing the Build Locally
# Build and preview locally npm run build npm run preview # The preview server shows exactly how your app will look on production
Nginx Configuration for Static Sites
Nginx serves your Vite-built static files with proper caching, compression, and security headers. This configuration handles the entire request flow from your domain to the VPS.
Step 1: Install Nginx
# Ubuntu/Debian sudo apt update sudo apt install nginx # Start and enable Nginx sudo systemctl start nginx sudo systemctl enable nginx
Step 2: Create Nginx Configuration
Create /etc/nginx/sites-available/vite-app.conf:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# Root directory - path to your dist folder
root /var/www/vite-app/dist;
index index.html;
# SSL certificates (set up with Certbot)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css text/javascript application/json application/javascript;
gzip_min_length 1000;
gzip_vary on;
# SPA routing - redirect 404s to index.html
location / {
try_files $uri $uri/ /index.html;
}
# Cache assets with hash for long periods
location ~* /assets/ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Cache other static files
location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 7d;
add_header Cache-Control "public, must-revalidate";
access_log off;
}
# Don't cache HTML
location ~* .html$ {
expires -1;
add_header Cache-Control "public, must-revalidate";
}
}Step 3: Enable Configuration
# Create symlink to enable the config sudo ln -s /etc/nginx/sites-available/vite-app.conf /etc/nginx/sites-enabled/ # Test Nginx configuration sudo nginx -t # Reload Nginx sudo systemctl reload nginx
SPA Routing with try_files
Single Page Applications (React, Vue, Svelte) handle their own routing on the client side. The critical Nginx directive for this is try_files.
How try_files Works
location / {
try_files $uri $uri/ /index.html;
}
# This directive tells Nginx:
# 1. Try to serve the requested file ($uri)
# 2. If not found, try as a directory ($uri/)
# 3. If still not found, serve index.htmlWithout this configuration, navigating to /products/123 would result in a 404 error because Nginx would look for a physical file at that path. With try_files, Nginx serves index.html, allowing your SPA router to handle the route.
Why This Matters
Without try_files:
- • User navigates to
/products/123 - • Nginx looks for file at
/products/123 - • File doesn't exist, returns 404 error
- • App breaks on page refresh
With try_files:
- • User navigates to
/products/123 - • Nginx looks for file at
/products/123 - • File doesn't exist, serves
/index.htmlinstead - • JavaScript router processes the URL and shows correct page
- • App works perfectly on refresh
Gzip Compression and Performance
Gzip compression dramatically reduces file sizes, improving load times. Most modern browsers support it, and Nginx can compress content on-the-fly with minimal CPU overhead.
Nginx Gzip Configuration
# In server block or http block gzip on; gzip_vary on; # Add Vary header gzip_proxied any; gzip_comp_level 6; # 1-9, balance of speed vs compression gzip_types text/plain text/css text/javascript application/json application/javascript; gzip_min_length 1000; # Don't compress files smaller than 1KB # Don't gzip already compressed files gzip_disable msie6;
Performance Impact
Uncompressed
- • JavaScript bundle: ~250 KB
- • CSS bundle: ~80 KB
- • Total: ~330 KB
With Gzip
- • JavaScript bundle: ~70 KB
- • CSS bundle: ~20 KB
- • Total: ~90 KB (73% reduction)
For a typical Vite React app, gzip compression reduces the initial payload by 60-80%. This has a massive impact on page load time, especially on slower connections.
Cache Headers for Assets
Vite's hashed filenames enable aggressive caching. When you deploy a new version, hashes change, so browsers fetch new files. Old files can stay cached forever.
Cache Strategy
# Vite generates files like: main-a1b2c3d4.js
# Since filename changes when content changes, cache aggressively
location ~* /assets/ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# index.html should NOT be cached (or cached briefly)
# Because it references the asset filenames
location ~* .html$ {
expires -1;
add_header Cache-Control "public, must-revalidate";
}
# Other static files: cache for 7 days
location ~* .(jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public, must-revalidate";
}How This Works
When You Deploy Version 1:
- •
index.html→main-abc123.js - • Browser caches
main-abc123.jsfor 30 days
When You Deploy Version 2:
- • Code changes → content hash changes
- •
index.html→main-xyz789.js(different hash) - • Browser fetches new file (different filename)
- • Old
main-abc123.jsstays cached, never used
This ensures users always get the latest version while maximizing cache efficiency.
Custom Base Path Configuration
If you're deploying your Vite app to a subdirectory (e.g., yourdomain.com/myapp instead of a root domain), you need to configure the base path.
Scenario: Subdirectory Deployment
If serving from yourdomain.com/myapp:
// vite.config.js
export default defineConfig({
base: '/myapp/', // Must end with /
plugins: [react()],
})The base option ensures all asset references are relative to your subdirectory. Without it, the app looks for assets at the root domain and breaks.
Nginx Configuration for Subdirectory
server {
listen 443 ssl http2;
server_name yourdomain.com;
root /var/www/myapp;
# Main app subdirectory
location /myapp/ {
alias /var/www/myapp/dist/;
try_files $uri $uri/ /index.html;
}
# Other app endpoints (optional)
location /api/ {
proxy_pass http://backend-server:3000;
}
}Always use trailing slashes for base paths: /myapp/ not /myapp
Environment Variables in Vite
Vite exposes environment variables through import.meta.env. Variables prefixed with VITE_ are available in the browser; others are server-only.
Setting Up Environment Variables
# .env (used in all environments) VITE_API_URL=https://api.example.com DATABASE_URL=postgres://... # Not exposed to browser # .env.production (used only in production) VITE_API_URL=https://api-prod.example.com VITE_ANALYTICS_KEY=abc123xyz # .env.development (used in dev) VITE_API_URL=http://localhost:3000 VITE_DEBUG=true
Using Variables in Code
// Only VITE_* variables are available to browser JavaScript
const apiUrl = import.meta.env.VITE_API_URL;
const isProduction = import.meta.env.PROD; // true in production build
const isDevelopment = import.meta.env.DEV; // true in development
// This will be undefined - not exposed to browser
console.log(import.meta.env.DATABASE_URL); // undefined
export function fetchData() {
return fetch(`${apiUrl}/data`);
}For VPS Deployment
# On your VPS, create .env.production file # in your project directory before building VITE_API_URL=https://yourdomain.com/api VITE_APP_NAME=Production App # Build with production environment npm run build # Automatically uses .env.production # Files are baked into the build - they're not runtime configurable # If you need to change them, rebuild with new .env values
Environment variables are baked into the build. Never commit sensitive keys to version control.
Common Mistakes and How to Avoid Them
Mistake 1: SPA 404 on Page Refresh (Missing try_files)
Without proper Nginx configuration, users get 404 when refreshing a page with a client-side route.
# ❌ WRONG - No try_files
location / {
root /var/www/vite-app/dist;
index index.html;
}
# ✅ CORRECT - Redirect 404s to index.html
location / {
root /var/www/vite-app/dist;
try_files $uri $uri/ /index.html;
}Mistake 2: Caching index.html Forever
If you cache index.html with Cache-Control: max-age=31536000, users won't see updates for a year!
# ❌ WRONG - HTML cached too long add_header Cache-Control "public, max-age=31536000"; # ✅ CORRECT - HTML not cached (or cached briefly) add_header Cache-Control "public, must-revalidate"; # Why? Because index.html changes on every build # It references asset files with new hashes # If HTML is cached, users won't fetch the new asset references
Mistake 3: Wrong Base Path or Missing Trailing Slash
Incorrect base path configuration breaks asset loading and routing.
// ❌ WRONG - Missing trailing slash
export default defineConfig({
base: '/myapp', // Will cause issues
})
// ✅ CORRECT - Trailing slash included
export default defineConfig({
base: '/myapp/',
})
// If deployed at root:
// ✅ CORRECT - Empty string or /
export default defineConfig({
base: '/',
})Mistake 4: Serving Wrong Directory
Point Nginx to your build output directory, not the project root.
# ❌ WRONG - Points to project root root /var/www/vite-app; # ✅ CORRECT - Points to built output root /var/www/vite-app/dist; # Directory structure: # /var/www/vite-app/ # ├── src/ # ├── dist/ ← Nginx should point here # │ ├── index.html # │ ├── assets/ # │ └── ... # ├── vite.config.js # └── package.json
Mistake 5: Environment Variables Not Baked Into Build
Building locally with development .env, then deploying to production won't use production environment variables.
# ❌ WRONG - Build locally with dev env npm run build # Uses .env and .env.development # Then upload dist/ to VPS # Production variables are never used! # ✅ CORRECT - Build on VPS or CI/CD with production env # 1. Create .env.production on VPS VITE_API_URL=https://yourdomain.com/api # 2. Build on VPS with production variables NODE_ENV=production npm run build # 3. Upload built dist/ folder # Variables are now baked into the build
Mistake 6: Forgetting to Disable SourceMaps in Production
Source maps expose your original source code to anyone who downloads them. Disable them in production.
// ❌ WRONG - Source maps included in production
export default defineConfig({
build: {
sourcemap: true,
},
})
// ✅ CORRECT - Disable source maps for production
export default defineConfig({
build: {
sourcemap: false,
},
})
// Or only enable them for staging/internal use
export default defineConfig({
build: {
sourcemap: process.env.NODE_ENV === 'staging',
},
})Automating with DeployWise
DeployWise automates the entire Vite deployment process, from building your app to configuring Nginx with proper caching, compression, and SPA routing. Deploy in minutes instead of hours.
What DeployWise Handles Automatically
Quick Start with DeployWise
# 1. Sign up and connect your VPS to DeployWise # 2. Create a new project and select your Git repository # 3. DeployWise detects your Vite project # 4. Configure deployment settings: # - Domain (yourdomain.com) # - Environment variables (VITE_API_URL, etc.) # - Build command (default: npm run build) # 5. Deploy with a single click # That's it! DeployWise handles: # - Building your Vite project # - Uploading to VPS # - Configuring Nginx (with try_files for SPA) # - Setting up gzip and caching # - Installing SSL certificates # - Monitoring and logs
DeployWise Benefits for Vite
- •Zero configuration - handles all Vite-specific setup
- •Intelligent caching based on hashed filenames
- •One-click deployments with rollback capability
- •Automatic gzip compression and optimization
- •Git integration with automatic deployments on push
- •Real-time logs and performance monitoring
Ready to Deploy Your Vite App?
Skip the manual Nginx configuration and deploy your Vite application to VPS in minutes with DeployWise. Perfect for React, Vue, Svelte, and other Vite-based projects.
Get Started with DeployWiseQuick Reference: Deployment Checklist
- Build project locally with npm run build
- Preview build with npm run preview
- Upload dist/ directory to VPS
- Install and configure Nginx
- Add try_files for SPA routing
- Enable gzip compression
- Configure cache headers
- Install SSL certificate with Certbot
- Test SPA routing and asset loading
- Monitor logs and performance