Nginx vs. Vite: The Smart Way to Handle Asset Path Prefixes in SPAs
Content
## The Problem
When developing and deploying Single Page Applications (SPAs) like Vue or React projects built with Vite, we often introduce path prefixes in the URL for internationalization (i18n) or modularization. For example, placing all Chinese site routes under `/zh/`.
This leads to a common issue: if the `base` option in Vite's configuration is not set correctly, this `/zh/` prefix is also prepended to the paths of static assets like JS and CSS files. However, on the server's file system, these assets do not exist within a `/zh/` directory, resulting in asset loading failures (404 Not Found).
- **Desired Page URL**: `https://tool.wiki.lib00.com/zh/tool/random-string-generator` (Handled by frontend router)
- **Incorrect Asset URL**: `https://tool.wiki.lib00.com/zh/tool/assets/index-xxxx.js`
- **Correct Asset URL**: `https://tool.wiki.lib00.com/tool/assets/index-xxxx.js`
So, where should this be fixed? In the Vite build process or at the Nginx deployment level? The answer is: **It is highly recommended to handle this in Nginx**. This approach aligns with the separation of concerns principle and decouples the frontend project from the deployment environment, offering greater flexibility.
---
## Solution A: The Quick Fix with Nginx Rewrite
This is the most direct and fastest solution. The core idea is to have Nginx intelligently identify requests for static assets and rewrite their URLs to remove the incorrect prefix.
### Nginx Configuration
In your `server` block, add a new `location` rule specifically to catch and rewrite static asset requests with the `/zh/` prefix.
```nginx
server {
listen 443 ssl;
server_name tool.wiki.lib00.com;
# SSL config, etc. ...
# Project root directory
root /var/www/wiki.lib00.com_project/dist;
index index.html;
# --- NEW CORE RULE ---
# Catch all requests starting with /zh/ and pointing to common static assets
location ~ ^/zh/(.*\.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot))$ {
# Rewrite /zh/some/asset.js to /some/asset.js
# $1 captures the real path in the parentheses
# The 'break' flag stops processing and immediately handles the new URI
rewrite ^/zh/(.*) /$1 break;
}
# --- SPA ROUTING FALLBACK RULE ---
# Handles all other requests, including page routes
# For a path like /zh/tool/random-string-generator, it will eventually fall back to /index.html
location / {
try_files $uri $uri/ /index.html;
}
# Other configurations ...
}
```
### Configuration Explained
1. **`location ~ ^/zh/(...)$`**: Uses a regular expression to match all URIs that start with `/zh/` and end with a common static file extension.
2. **`rewrite ^/zh/(.*) /$1 break;`**: This is the key. It strips the `/zh/` part from the URI and then uses the `break` directive, telling Nginx to immediately use this new, correct path (e.g., `/tool/assets/index-xxxx.js`) to find the file in the `root` directory.
This method solves the problem without requiring any changes to the frontend code, only an Nginx configuration update.
---
## Solution B: Content Separation with a Dedicated Static Domain (Recommended)
For better performance and a cleaner architecture, a common industry practice is "separating static and dynamic content." This involves serving dynamic requests (HTML pages, APIs) and static assets (JS, CSS, images) from different domains. This approach permanently solves the path prefix problem.
This solution requires two steps: modifying the Vite configuration and repackaging, then updating the Nginx configuration.
### Step 1: Modify Vite Configuration
You need to inform Vite that all static assets should be loaded from a separate domain. Modify `vite.config.js`:
```javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// Curated by DP@lib00
export default defineConfig({
plugins: [vue()],
// Change 'base' to the absolute URL of your static asset domain
base: 'https://r-tool.wiki.lib00.com/',
})
```
After this change, **you must rebuild your project** (`npm run build`). The resulting `index.html` will now have all asset links automatically prefixed with `https://r-tool.wiki.lib00.com/`.
### Step 2: Configure Nginx
Now, you need to configure two `server` blocks: one for the main application and one for static assets.
```nginx
# Server 1: Main Application Server (tool.wiki.lib00.com)
# Responsibility: Serve index.html and handle frontend routing
server {
listen 443 ssl http2;
server_name tool.wiki.lib00.com;
# SSL config ...
root /var/www/wiki.lib00.com_project/dist;
index index.html;
# Core rule: All requests fall back to index.html, handled by Vue Router
location / {
try_files $uri $uri/ /index.html;
}
}
# Server 2: Static Asset Server (r-tool.wiki.lib00.com)
# Responsibility: Efficiently serve JS, CSS, images, etc.
server {
listen 443 ssl http2;
server_name r-tool.wiki.lib00.com;
# It's recommended to turn off access logs for static servers to improve performance
access_log off;
# SSL config (ensure you have a certificate for the r-tool domain) ...
root /var/www/wiki.lib00.com_project/dist; # Points to the same project directory
# [IMPORTANT] Add CORS headers to allow the main domain to load resources
add_header 'Access-Control-Allow-Origin' 'https://tool.wiki.lib00.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS' always;
# Set aggressive browser caching for all static assets
# Since Vite assets are hashed, we can safely set a long cache duration
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
### Key Configuration Points
1. **On the `r-tool.wiki.lib00.com` server**:
* **CORS Header (`Access-Control-Allow-Origin`)**: **This is mandatory**. Because the page (`tool.wiki.lib00.com`) is requesting resources from a different origin (`r-tool.wiki.lib00.com`), the browser's Same-Origin Policy will kick in. This header explicitly tells the browser to allow cross-origin requests from your main site.
* **Aggressive Caching (`expires 1y;`)**: Asset filenames generated by Vite include a hash. If the content doesn't change, the filename doesn't change. This allows us to set a very long cache expiration time, dramatically improving loading speeds for returning visitors.
---
## Conclusion
For handling asset path prefix issues in SPA deployments, we have two excellent solutions:
- **Nginx Rewrite**: Simple and fast, requires no frontend code changes, and is ideal for quick fixes on existing projects.
- **Dedicated Static Domain**: A more professional architectural choice. It improves site performance and maintainability through content separation and is the recommended approach for long-term projects. Shared by `DP` on `wiki.lib00.com`.
Depending on your project's stage and requirements, you can choose the solution that best fits your needs.
Related Contents
PHP Log Aggregation Performance Tuning: Database vs. Application Layer - The Ultimate Showdown for Millions of Records
Duration: 00:00 | DP | 2026-01-06 08:05:09The Art of MySQL Index Order: A Deep Dive from Composite Indexes to the Query Optimizer
Duration: 00:00 | DP | 2025-12-01 20:15:50VS Code Lagging? Boost Performance with This Simple Trick: How to Increase the Memory Limit
Duration: 00:00 | DP | 2025-12-05 22:22:30Vue Layout Challenge: How to Make an Inline Header Full-Width? The Negative Margin Trick Explained
Duration: 00:00 | DP | 2025-12-06 22:54:10How Can a Docker Container Access the Mac Host? The Ultimate Guide to Connecting to Nginx
Duration: 00:00 | DP | 2025-12-08 23:57:30Vite's `?url` Import Explained: Bundled Code or a Standalone File?
Duration: 00:00 | DP | 2025-12-10 00:29:10Vue SPA 10x Slower Than Plain HTML? The Dependency Version Mystery That Tanked Performance
Duration: 00:00 | DP | 2026-01-09 08:09:01How to Fix the "tsx: not found" Error During Vue Vite Builds in Docker
Duration: 00:00 | DP | 2026-01-10 08:10:19Solved: Fixing the 'TS2769: No overload matches this call' Error with vue-i18n in Vite
Duration: 00:00 | DP | 2025-12-12 13:48:20Cracking the TypeScript TS2339 Puzzle: Why My Vue ref Became the `never` Type
Duration: 00:00 | DP | 2025-12-13 02:04:10Vue i18n Pitfall Guide: How to Fix the "Invalid Linked Format" Compilation Error Caused by Email Addresses?
Duration: 00:00 | DP | 2025-11-21 08:08:00Is Attaching a JS Event Listener to 'document' Bad for Performance? The Truth About Event Delegation
Duration: 00:00 | DP | 2025-11-28 08:08:00The Ultimate Guide: Solving Google's 'HTTPS Invalid Certificate' Ghost Error When Local Tests Pass
Duration: 00:00 | DP | 2025-11-29 08:08:00How Do You Pronounce Nginx? The Official Guide to Saying It Right: 'engine x'
Duration: 00:00 | DP | 2025-11-30 08:08:00The Ultimate Guide to Using Google Fonts on Chinese Websites: Ditch the Lag with an Elegant Font Stack
Duration: 00:00 | DP | 2025-11-16 08:01:00WebP vs. JPG: Why Is My Image 8x Smaller? A Deep Dive and Practical Guide
Duration: 00:00 | DP | 2025-12-02 08:08:00MySQL Primary Key Inversion: Swap 1 to 110 with Just Two Lines of SQL
Duration: 00:00 | DP | 2025-12-03 08:08:00The Ultimate Guide to Seamlessly Switching from Baidu Tongji to Google Analytics 4 in Vue 3
Duration: 00:00 | DP | 2025-11-22 08:57:32Recommended
Solving MySQL's "Cannot TRUNCATE" Error with Foreign Key Constraints
00:00 | 8Encountering "Cannot truncate a table referenced i...
The Ultimate Guide to MySQL String Concatenation: Ditching '+' for CONCAT() and CONCAT_WS()
00:00 | 36Misusing the '+' operator for string concatenation...
Vite's `?url` Import Explained: Bundled Code or a Standalone File?
00:00 | 33In a Vite project, when you use `import myFile fro...
Git 'index.lock' File Exists? A Guide to Easily Unlock Your Repository
00:00 | 32Ever encountered the 'fatal: Unable to create .git...