Mastering Nginx for Nuxt 3 Deployments: A Deep Dive into Production-Ready Configurations
Nuxt 3 has revolutionized the way we build performant, full-stack Vue.js applications. With its powerful Nitro server engine, hybrid rendering capabilities, and fantastic developer experience, it’s quickly become a top choice for modern web projects. But deploying a Nuxt 3 application to production, especially with server-side rendering (SSR) or a hybrid approach, requires a robust and efficient web server to stand in front of it. Enter Nginx.
Nginx, pronounced “engine-x”, is a high-performance web server, reverse proxy, and load balancer. Its asynchronous, event-driven architecture makes it incredibly efficient at handling concurrent connections, making it the perfect companion for a Nuxt 3 application destined for the real world.
In this deep dive, we’ll explore how to configure Nginx to serve your Nuxt 3 applications, covering everything from basic reverse proxying to advanced caching, security headers, and best practices that ensure your deployment is not just functional, but production-ready, fast, and secure.
Nuxt 3’s Build Output: Understanding the Target
Before we dive into Nginx, let’s briefly recap how Nuxt 3 builds for production. When you run npm run build (or pnpm build, yarn build), Nuxt compiles your application into a .output directory.
Inside .output, you’ll typically find:
server/index.mjs: This is your Nuxt 3 Nitro server entry point. When running in SSR or hybrid mode, this is the process that Nginx will proxy requests to.public/: This directory contains all your static assets (CSS, JS bundles, images, fonts, etc.) that the Nuxt app needs, as well as statically generated HTML files if you’re usingnpx nuxi generate.
Understanding these two main outputs is crucial for configuring Nginx correctly, as we’ll be directing Nginx to either serve static files directly or proxy requests to the running Nitro server.
The Foundation: Nginx as a Reverse Proxy for Nuxt 3 SSR/Hybrid
For Nuxt 3 applications leveraging SSR or hybrid rendering, your Nuxt app will typically run as a Node.js process on a specific port (e.g., 3000). Nginx acts as a reverse proxy, sitting in front of your Nuxt process. This setup offers several benefits:
- Security: Nginx acts as a buffer, preventing direct access to your application server.
- Performance: Nginx can handle SSL termination, compression, and static asset serving much more efficiently than a Node.js process.
- Load Balancing: Easily scale by adding more Nuxt instances behind Nginx.
- Flexibility: Manage multiple applications on the same server or domain.
Let’s start with a basic Nginx configuration for a Nuxt 3 SSR application. This example assumes your Nuxt 3 app is running on http://localhost:3000.
# /etc/nginx/sites-available/your-nuxt-app.conf
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP to HTTPS later (best practice)
# For now, we'll serve directly over HTTP
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Important for real client IP
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;
}
}
Explanation of directives:
listen 80;: Nginx listens for incoming HTTP requests on port 80.server_name yourdomain.com www.yourdomain.com;: Specifies the domain names this server block responds to.location / { ... }: This block handles all requests to the root path/.proxy_pass http://localhost:3000;: The core of reverse proxying. All requests matching this location are forwarded to your Nuxt app running on port 3000.proxy_http_version 1.1;: Essential for supporting WebSockets if your Nuxt app uses them.proxy_set_header ...: These headers ensure that your Nuxt app receives crucial client information (like the actual IP address, host, and protocol) that would otherwise be lost when proxied through Nginx.
To activate this configuration, create a symbolic link from sites-available to sites-enabled:
sudo ln -s /etc/nginx/sites-available/your-nuxt-app.conf /etc/nginx/sites-enabled/
Then test your Nginx configuration: sudo nginx -t and reload Nginx: sudo systemctl reload nginx.
Optimizing Static Asset Serving
While proxy_pass sends all requests to your Nuxt app, it’s far more efficient to let Nginx serve static assets directly. Nginx is purpose-built for this and will offload this task from your Node.js process, freeing it up for rendering dynamic content.
In your Nuxt 3 .output/public directory, you’ll find your optimized assets. Let’s configure Nginx to serve these directly.
# /etc/nginx/sites-available/your-nuxt-app.conf (within your existing server block)
server {
# ... (previous listen and server_name directives)
# Serve static assets directly from .output/public
location ~ ^/(assets|img|_nuxt|favicon.ico|robots.txt) {
root /path/to/your/nuxt3/project/.output/public;
try_files $uri $uri/ =404; # Ensure files exist, otherwise return 404
expires 1y; # Aggressive caching for static assets
add_header Cache-Control "public, immutable"; # Indicate assets won't change
# Gzip compression (highly recommended for performance)
gzip_static on; # If you pre-compress with Vite, otherwise use gzip on;
gzip on;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_comp_level 5;
}
# All other requests are proxied to the Nuxt 3 server
location / {
proxy_pass http://localhost:3000;
# ... (previous proxy_set_header directives)
}
}
Key additions:
location ~ ^/(assets|img|_nuxt|favicon.ico|robots.txt): This regex matches common static asset paths generated by Nuxt 3 and other typical static files. Adjust as needed if you have custom static directories._nuxtis particularly important as it contains your client-side JavaScript bundles.root /path/to/your/nuxt3/project/.output/public;: Crucially, thisrootdirective points directly to thepublicdirectory within your Nuxt 3’s.outputfolder. Make sure to replace/path/to/your/nuxt3/projectwith the actual absolute path on your server.try_files $uri $uri/ =404;: Nginx will try to serve the exact file, then a directory (if applicable), and if neither is found, it returns a 404.expires 1y;&add_header Cache-Control "public, immutable";: These headers tell the browser to cache these assets aggressively, significantly speeding up subsequent visits.immutableis ideal for versioned assets (like those with hashes in their filenames).gzip_static on;: If you’ve pre-compressed your assets (e.g., using a build tool that generates.gzfiles), Nginx can serve these directly. Otherwise,gzip on;enables on-the-fly compression.
Nginx for Static Nuxt 3 Deployments (npx nuxi generate)
If you’re building a purely static Nuxt 3 application (e.g., for a blog or marketing site) using npx nuxi generate, your deployment strategy is simpler. There’s no Node.js process to proxy to; Nginx just needs to serve the static files, and handle client-side routing.
# /etc/nginx/sites-available/your-static-nuxt-app.conf
server {
listen 80;
server_name yourstaticdomain.com www.yourstaticdomain.com;
root /path/to/your/nuxt3/project/.output/public; # Point directly to the public folder
# Serve static files and handle client-side routing
location / {
try_files $uri $uri/ /index.html; # Crucial for SPA routing
expires 1y;
add_header Cache-Control "public, immutable"; # For versioned assets
gzip on; # Enable gzip for all static content
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_comp_level 5;
}
}
The key here is try_files $uri $uri/ /index.html;. This directive tells Nginx:
- Try to serve the requested URI as a file.
- If it’s a directory, try to serve
index.htmlfrom that directory. - If neither of the above works (e.g., a direct URL like
/aboutthat isn’t a physical file), fall back to serving/index.html. This allows Nuxt’s client-side router to take over and render the correct page without a full server reload, which is fundamental for Single Page Application (SPA) behavior.
Advanced Nginx for Production: Security & Performance
Now, let’s enhance our SSR/Hybrid Nginx configuration with crucial security and performance directives.
1. SSL/TLS (HTTPS) - A Must-Have!
Serving your application over HTTPS is non-negotiable in the current web landscape. Let’s Encrypt provides free SSL certificates, and Certbot automates the process.
After obtaining your certificates (e.g., from Certbot, typically found in /etc/letsencrypt/live/yourdomain.com/), your Nginx config will look something like this:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri; # Redirect HTTP to HTTPS
}
server {
listen 443 ssl http2; # Listen on port 443 with SSL and HTTP/2
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
# HSTS (Strict Transport Security) - enforce HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# ... (include your location blocks for static assets and proxy_pass here)
}
2. Comprehensive Security Headers
Beyond HSTS, Nginx can add other critical security headers to mitigate common web vulnerabilities.
# ... (inside your 443 server block, usually at the top)
add_header X-Frame-Options "SAMEORIGIN" always; # Prevents clickjacking
add_header X-Content-Type-Options "nosniff" always; # Prevents MIME-sniffing vulnerabilities
add_header X-XSS-Protection "1; mode=block" always; # Enables XSS filters in some browsers
add_header Referrer-Policy "no-referrer-when-downgrade" always; # Controls referrer information
# Content-Security-Policy (CSP) is more complex but highly recommended
# add_header Content-Security-Policy "default-src 'self';" always;
3. Nginx Caching for Dynamic Content (SSR/Hybrid)
While Nuxt 3’s Nitro server is fast, Nginx can cache responses for dynamic pages, further reducing the load on your Nuxt app for frequently accessed, non-personalized content.
First, define a cache zone (e.g., in http block or above server blocks):
# /etc/nginx/nginx.conf or a separate file included in http block
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nuxt_cache:10m max_size=1g inactive=60m use_temp_path=off;
Then, integrate it into your location / block:
# ... (inside your 443 server block, location / block)
location / {
proxy_cache nuxt_cache;
proxy_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
proxy_cache_valid 404 1m; # Cache 404s for 1 minute
proxy_cache_bypass $http_pragma $http_authorization; # Don't cache if Pragma or Authorization headers are present
proxy_no_cache $http_pragma $http_authorization; # Don't send cached response if Pragma or Authorization are present
add_header X-Proxy-Cache $upstream_cache_status; # Useful for debugging cache status
# ... (other proxy_pass and proxy_set_header directives)
}
Gotchas with caching dynamic content:
- User-specific content: Be very careful with caching pages that contain personalized user data. Use
proxy_cache_bypassandproxy_no_cacheor avoid caching such routes. - Vary Header: Ensure your Nuxt application sets the
Varyheader (e.g.,Vary: Accept-Encoding) if content changes based on client capabilities. Nginx respects this. - Cache Invalidation: For critical updates, you’ll need a strategy to invalidate the Nginx cache (e.g., by restarting Nginx or using
proxy_cache_purge).
Putting It All Together: A Comprehensive Nuxt 3 SSR Nginx Config
# /etc/nginx/sites-available/your-nuxt-app.conf
# Define Nginx cache zone (can be in nginx.conf http block too)
# proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nuxt_cache:10m max_size=1g inactive=60m use_temp_path=off;
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL Configuration (adjust paths to your certificates)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" 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;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Root directory for your Nuxt 3 project's output (important for static assets)
# Ensure this points to the absolute path of your Nuxt project's .output/public
root /var/www/your-nuxt-app/.output/public;
# Serve static assets directly and aggressively cache them
location ~ ^/(assets|img|_nuxt|favicon.ico|robots.txt) {
# 'root' is already defined at server block level
try_files $uri $uri/ =404;
expires 1y;
add_header Cache-Control "public, immutable";
# Gzip compression for static files
gzip_static on; # If pre-compressed assets are available
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml+rss text/javascript image/svg+xml;
}
# Proxy all other requests to the Nuxt 3 Node.js application
location / {
proxy_pass http://localhost:3000; # Your Nuxt 3 app's listening port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
# Optional: Nginx proxy caching for dynamic content
# proxy_cache nuxt_cache;
# proxy_cache_valid 200 302 10m;
# proxy_cache_bypass $http_pragma $http_authorization;
# proxy_no_cache $http_pragma $http_authorization;
# add_header X-Proxy-Cache $upstream_cache_status;
# Improve proxy buffering for potentially slow client connections
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
Best Practices and Final Tips
- Process Management: Use a process manager like PM2 or
systemdto keep your Nuxt 3 Node.js process running and automatically restart it if it crashes. - Logging: Monitor both Nginx access/error logs (
/var/log/nginx/) and your Nuxt application’s logs for any issues. - Separation of Concerns: Keep your Nginx configuration clean by separating server blocks into individual files in
sites-availableand linking them tosites-enabled. - Testing: Always run
sudo nginx -tafter making changes to your Nginx configuration files to catch syntax errors before reloading. - Security Updates: Keep Nginx and your OS up to date to patch any known vulnerabilities.
- Nuxt’s
server/apiroutes: Remember that any API routes defined in your Nuxt 3server/apidirectory will also be handled by the Nitro server, so theproxy_passconfiguration correctly forwards these requests. - Resource Limits: For high-traffic applications, consider increasing
worker_processesinnginx.confto match your CPU cores.
Mastering Nginx for your Nuxt 3 deployments is a crucial step towards building high-performing, secure, and scalable web applications. By carefully configuring Nginx to handle static assets, proxy dynamic requests, and enforce security, you create a robust environment that lets your Nuxt app shine.
Discussion Questions for Readers:
- What Nginx optimizations have you found most impactful for your Nuxt 3 deployments, especially when dealing with high traffic or specific rendering modes (SSR, ISR, Static)?
- Are there specific Nginx modules or advanced configurations you swear by for enhancing Nuxt 3’s performance or security beyond what was covered here? Share your insights!