# Nginx try_files in Modern Microservices with Vue 3
## Table of Contents
1. [Introduction](#Introduction)
2. [Understanding try_files](#Understanding-try_files)
3. [Vue 3 SPA Challenges](#Vue-3-SPA-Challenges)
4. [Microservices Architecture](#Microservices-Architecture)
5. [Basic Configuration](#Basic-Configuration)
6. [Advanced Patterns](#Advanced-Patterns)
7. [Real-world Examples](#Real-world-Examples)
8. [Best Practices](#Best-Practices)
9. [Troubleshooting](#Troubleshooting)
## Introduction
In modern web development, Single Page Applications (SPAs) like Vue 3 apps are often deployed in microservices architectures. This creates unique challenges for web servers like Nginx, particularly around routing and file serving. The `try_files` directive is a powerful tool that helps solve these challenges elegantly.
## Understanding try_files
### What is try_files?
The `try_files` directive in Nginx attempts to serve files in a specified order. It's essential for SPAs because client-side routing needs to fall back to the main application file when direct file access fails.
### Basic Syntax
```nginx
try_files $uri $uri/ @fallback;
```
**Parameters:**
- `$uri` - The exact URI requested
- `$uri/` - The URI with a trailing slash (for directories)
- `@fallback` - A named location block for final fallback
## Vue 3 SPA Challenges
### Client-Side Routing Problem
Vue 3 applications use client-side routing (Vue Router), which means:
1. **Initial Load**: `example.com/` loads `index.html`
2. **Navigation**: `example.com/dashboard` should also load `index.html`
3. **Direct Access**: Typing `example.com/dashboard` in browser fails without proper server configuration
### The 404 Problem
Without proper configuration:
```
GET /dashboard → 404 Not Found
```
With `try_files`:
```
GET /dashboard → try_files → serve index.html → Vue Router handles /dashboard
```
## Microservices Architecture
### Typical Setup
```
┌───────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Load Balancer │────│ Nginx │────│ Vue 3 App │
│ │ │ (Gateway) │ │ (Frontend) │
└───────────────┘ └──────────────┘ └─────────────────┘
│
├─────┐
│ │
▼ ▼
┌─────────┐ ┌─────────────┐
│ API │ │ Auth │
│ Service │ │ Service │
└─────────┘ └─────────────┘
```
### Routing Challenges
1. **Frontend Routes**: `/`, `/dashboard`, `/profile`
2. **API Routes**: `/api/users`, `/api/orders`
3. **Service Routes**: `/auth/login`, `/auth/logout`
## Basic Configuration
### Simple Vue 3 Configuration
```nginx
server {
listen 80;
server_name example.com;
root /var/www/vue-app/dist;
index index.html;
# Handle Vue Router (client-side routing)
location / {
try_files $uri $uri/ /index.html;
}
# Static assets with caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
}
```
### With API Proxy
```nginx
server {
listen 80;
server_name example.com;
root /var/www/vue-app/dist;
index index.html;
# API proxy to microservices
location /api/ {
proxy_pass http://api-backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Frontend application
location / {
try_files $uri $uri/ /index.html;
}
}
upstream api-backend {
server api-service-1:3000;
server api-service-2:3000;
server api-service-3:3000;
}
```
## Advanced Patterns
### Multi-Environment Setup
```nginx
# Development Environment
server {
listen 80;
server_name dev.example.com;
root /var/www/vue-app-dev/dist;
location /api/ {
proxy_pass http://dev-api-backend;
}
location / {
try_files $uri $uri/ /index.html;
}
}
# Production Environment
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/vue-app-prod/dist;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
location /api/ {
proxy_pass https://prod-api-backend;
}
location / {
try_files $uri $uri/ /index.html;
}
}
```
### Subdirectory Deployments
```nginx
# Vue app deployed in subdirectory
location /app/ {
alias /var/www/vue-app/dist/;
try_files $uri $uri/ /app/index.html;
}
# API services
location /app/api/ {
rewrite ^/app/api/(.*) /api/$1 break;
proxy_pass http://api-backend;
}
```
### Multiple SPAs
```nginx
server {
listen 80;
server_name example.com;
# Admin Panel (Vue 3)
location /admin/ {
alias /var/www/admin-panel/dist/;
try_files $uri $uri/ /admin/index.html;
}
# Customer Portal (Vue 3)
location /portal/ {
alias /var/www/customer-portal/dist/;
try_files $uri $uri/ /portal/index.html;
}
# Main Website (Vue 3)
location / {
root /var/www/main-site/dist;
try_files $uri $uri/ /index.html;
}
}
```
## Real-world Examples
### E-commerce Microservices
```nginx
server {
listen 443 ssl http2;
server_name shop.example.com;
root /var/www/vue-shop/dist;
# Product API
location /api/products/ {
proxy_pass http://product-service;
}
# User API
location /api/users/ {
proxy_pass http://user-service;
}
# Order API
location /api/orders/ {
proxy_pass http://order-service;
}
# Payment API
location /api/payments/ {
proxy_pass http://payment-service;
}
# Static assets optimization
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Vue 3 SPA - must be last
location / {
try_files $uri $uri/ /index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
}
# Backend services
upstream product-service {
server product-api-1:3001;
server product-api-2:3001;
}
upstream user-service {
server user-api-1:3002;
server user-api-2:3002;
}
upstream order-service {
server order-api-1:3003;
server order-api-2:3003;
}
upstream payment-service {
server payment-api-1:3004;
server payment-api-2:3004;
}
```
### Development vs Production
```nginx
# Development Configuration
map $http_host $environment {
~^dev\. "development";
~^staging\. "staging";
default "production";
}
server {
listen 80;
server_name ~^(?<subdomain>.+)\.example\.com$;
set $app_root /var/www/vue-app-$environment/dist;
root $app_root;
# Development: Disable caching
location ~* \.(js|css)$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
try_files $uri =404;
}
# Production: Aggressive caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# API routing based on environment
location /api/ {
proxy_pass http://$environment-api-backend;
}
# Vue 3 SPA
location / {
try_files $uri $uri/ /index.html;
}
}
```
## Best Practices
### 1. Order Matters
```nginx
# ❌ Wrong - too broad location blocks first
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend; # This will never be reached!
}
# ✅ Correct - specific locations first
location /api/ {
proxy_pass http://backend;
}
location / {
try_files $uri $uri/ /index.html;
}
```
### 2. Asset Optimization
```nginx
# Cache static assets aggressively
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
# Enable gzip compression
gzip_static on;
}
# Don't cache HTML files
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
try_files $uri $uri/ /index.html;
}
```
### 3. Security Headers
```nginx
location / {
try_files $uri $uri/ /index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
}
```
### 4. Health Checks
```nginx
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Detailed status for monitoring
location /nginx-status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
```
### 5. Error Handling
```nginx
# Custom error pages
error_page 404 /index.html; # Let Vue Router handle 404s
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/error-pages;
}
# Fallback for API errors
location /api/ {
proxy_pass http://backend;
# Custom error handling
proxy_intercept_errors on;
error_page 502 503 504 /api-error.json;
}
location = /api-error.json {
return 503 '{"error": "Service temporarily unavailable"}';
add_header Content-Type application/json;
}
```
## Troubleshooting
### Common Issues
#### 1. 404 on Refresh
**Problem**: Direct URL access returns 404
```
GET /dashboard → 404 Not Found
```
**Solution**: Ensure try_files is configured correctly
```nginx
location / {
try_files $uri $uri/ /index.html;
}
```
#### 2. API Calls Being Intercepted
**Problem**: API calls are being served index.html
```
GET /api/users → Returns index.html instead of JSON
```
**Solution**: Place API location blocks before catch-all
```nginx
location /api/ {
proxy_pass http://backend;
}
location / {
try_files $uri $uri/ /index.html;
}
```
#### 3. Assets Not Loading
**Problem**: Static assets return 404
```
GET /assets/app.js → 404 Not Found
```
**Solution**: Check file paths and try_files configuration
```nginx
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
try_files $uri =404; # Don't fallback to index.html for assets
}
```
### Debugging Commands
```bash
# Test nginx configuration
nginx -t
# Reload nginx
nginx -s reload
# Check access logs
tail -f /var/log/nginx/access.log
# Check error logs
tail -f /var/log/nginx/error.log
# Test specific URLs
curl -I http://example.com/dashboard
curl -I http://example.com/api/users
```
### Debug Configuration
```nginx
# Enable debug logging
error_log /var/log/nginx/debug.log debug;
server {
# Log try_files attempts
location / {
try_files $uri $uri/ /index.html;
# Debug headers
add_header X-Debug-Uri $uri;
add_header X-Debug-Args $args;
add_header X-Debug-Request-Uri $request_uri;
}
}
```
## Conclusion
The `try_files` directive is crucial for serving Vue 3 SPAs in microservices architectures. Key takeaways:
1. **Order matters**: Place specific location blocks before catch-all blocks
2. **Assets need special handling**: Don't let static assets fall back to index.html
3. **Security**: Always include appropriate headers
4. **Caching**: Optimize static assets but not HTML files
5. **Monitoring**: Include health checks and proper logging
With proper configuration, Nginx can efficiently serve Vue 3 applications while proxying API requests to appropriate microservices, creating a seamless user experience.
---
*This tutorial provides a comprehensive guide to using nginx try_files with Vue 3 in modern microservices architectures. For production deployments, always test configurations thoroughly and follow security best practices.*