How to Deploy Next.js to a Subdirectory using Nginx (The Right Way)

How to Deploy Next.js to a Subdirectory using Nginx properly

We have all been there.

You have a perfectly working Next.js application running on localhost:3000. It’s fast, the images load, and the routing is snappy.

Then, your boss (or client) drops the requirement: “We need this living at example.com/dashboard, not on a subdomain.”

You think, “Easy, I’ll just add a location /dashboard block in Nginx.” You deploy it, navigate to the URL, and… White Screen of Death.
Or maybe the HTML loads, but it looks like a 1990s website because every single CSS and JavaScript file is returning a 404 Not Found.

Deploying Next.js to a subdirectory is deceptively difficult. It involves a “Three-Way Handshake” between your next.config.js, your Nginx Proxy, and your Folder Structure. Get one slash wrong, and the whole thing breaks.

In this guide, I will walk you through the exact, battle-tested method to deploy Next.js 13/14+ (App Router or Pages Router) to a subdirectory without losing your mind.

The “Too Long; Didn’t Read” Solution:
You could manually fiddle with the 15 lines of Nginx config below and pray you get the proxy_pass trailing slash right… or you can just generate the perfect config code instantly here. It handles the math for you.

Why Subdirectories? (The SEO Argument)

Before we fix the code, let’s validate why you are doing this. Why not just use app.example.com?

Domain Authority Flow.
Google treats subdomains (app.example.com) almost like entirely separate websites. If your main domain example.com has a high Domain Authority (DA) from years of blogging, your new app on a subdomain starts from zero.

However, if you host it at example.com/app, it inherits all that “link juice” immediately. For SaaS tools, eCommerce micro-stores, and internationalization (/fr/, /de/), subdirectories are the superior architecture.

Step 1: The next.config.js Adjustment

The most common mistake developers make is trying to fix everything in Nginx. You cannot. Next.js needs to know it is living in a subfolder, or it will try to fetch assets from the root.

Open your next.config.js and add the basePath property.

/** @type {import('next').NextConfig} */
const nextConfig = {
  // If you are deploying to example.com/dashboard
  basePath: '/dashboard',
  
  // Optional: If you want your images to work automatically
  // images: { unoptimized: true } // Only if you have image loader issues
}

module.exports = nextConfig

What does basePath actually do?

  1. Routes: It prefixes every Link. <Link href="/settings"> automatically becomes /dashboard/settings.
  2. Assets: It tells Next.js to look for CSS/JS at /dashboard/_next/static/... instead of just /_next/static/....
Warning: Do NOT use assetPrefix unless you are serving assets from a separate CDN (like S3). For Nginx subdirectories, basePath is the correct tool.

Step 2: The Nginx Configuration (The Hard Part)

This is where 90% of deployments fail. Nginx’s proxy logic is strict.

If you are running your Next.js app on port 3000 using PM2 or similar, you need to “Reverse Proxy” the traffic from port 80/443 to port 3000.

Here is the Gold Standard configuration block. Open your config file (usually /etc/nginx/sites-available/default):

server {
    listen 80;
    server_name example.com;

    # ... other configs ...

    # The Block for your Subdirectory
    location /dashboard {
        # 1. Forward the traffic to localhost
        proxy_pass http://127.0.0.1:3000/dashboard;
        
        # 2. Standard Headers (Crucial for Next.js)
        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;
    }
}

The “Trailing Slash” Trap

Look closely at the proxy_pass line.

  • Correct: proxy_pass http://127.0.0.1:3000/dashboard;
  • Incorrect: proxy_pass http://127.0.0.1:3000/;

If you put a trailing slash at the end of the upstream URL, Nginx strips the subdirectory path. Since we used basePath: '/dashboard' in Step 1, Next.js expects to receive the request with /dashboard in the URL. If Nginx strips it, Next.js gets a request for / (root), sees no matching page (because the app is mounted at /dashboard), and throws a 404.

Step 3: Fixing the _next 404 Error

Sometimes, even with the above config, your main page loads, but your console lights up red with failures to load JavaScript bundles.

This happens because the browser requests:
GET https://example.com/dashboard/_next/static/chunks/main.js

But Nginx might get confused about where _next physically lives on the disk versus where it lives in the URL structure.

To bulletproof this, add a specific location block for the static assets (or better yet, let the proxy handle it correctly).

The better way is to trust the proxy. If you used the Config Generator, it gives you a proxy-based solution that doesn’t need physical file paths:

location /dashboard/_next/static {
    proxy_pass http://127.0.0.1:3000/dashboard/_next/static;
}

This keeps your operational logic strictly networked, meaning you can move the app folder anywhere and Nginx won’t care.

Step 4: Handling WebSockets (HMR)

If you are seeing "WebSocket connection failed" in your browser console, it means Nginx isn’t forwarding the “Upgrade” headers. Next.js uses WebSockets for Hot Module Replacement (HMR) in dev, and sometimes for Middleware in production.

This block is mandatory:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

Without this, your app feels sluggish, and real-time features (if you use them) will fail silently.

Troubleshooting Guide

Issue 1: “Too Many Redirects”

Cause: You likely have trailingSlash: true in next.config.js but your Nginx proxy_pass is formatted to strip slashes.

Fix: Ensure consistency. I generally recommend keeping trailingSlash: false (default) unless you have a specific reason to change it.

Issue 2: Images are Broken (next/image)

Cause: The Next.js Image Optimization API needs to know it’s behind a proxy.

Fix: Often, you need to pass the X-Forwarded-Proto header so Next.js knows it is on HTTPS.

proxy_set_header X-Forwarded-Proto $scheme;

Issue 3: CSS loads, but Links go to 404 page

Cause: You didn’t set basePath in next config. You probably hacked the Nginx rewrite to make the landing page work, but Next.js client-side routing still thinks it’s at /.

Fix: Go back to Step 1. Define the basePath.

Conclusion: Automate the Pain

Deploying to a subdirectory is one of those tasks you do once a year, struggle with for 4 hours, and then forget.

Next time, don’t waste the 4 hours.

If you are building a Monorepo, setting up a Micro-Frontend architecture, or just hosting a blog alongside your main app, use the standardized configs.

Click here to use the Nginx Config Generator. It takes your subdirectory name and port, and outputs the exact, copy-paste block you need to put in your sites-available file. It fixes the headers, the slash logic, and the static assets in one go.

Hosting structure matters. Don’t let a bad config file ruin your SEO.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top