Search code examples
dockernginxnext.jsdocker-compose

Serving a Nextj.s app on Docker with Nginx at a specific location


I am trying to serve a Next.js app at a specific location (mydomain.com/myapp) with Nginx through Docker. I can't get my configuration to correctly handle the base path.

So, with the above configuration, mydomain.com/myapp shows the app's 404 page, and the main page is served at mydomain.com/myapp/myapp. The assets aren't properly loaded too, they lead to 404 (that would be because the assetPrefix should then be /myapp/myapp).

I've played a bit with basePath configuration in next.config.js and the only way to get the app's index to be served at my wanted location is to remove the basePath but that breaks the routing of my app in a weird way. In that case, a Link to /page1 would lead to mydomain.com/page1 instead of mydomain.com/myapp/page1 and the page would still work if I navigate to it with the link. If I reload the page (or simply load this url), I get a 404 not found served by Nginx.

Questions

How can I get my nextjs-app to be served at mydomain.com/myapp with correct page routing and assets loaded?

I don't want to change all the routes to add the base path as it might change multiple times in the future. I thought about loading the base path value through an env variable but I think there might be a better normal configuration for this case that I'm simply missing.

Actual Configuration

My folder architecture is as follows:

.
├── docker-compose.yml
├── nginx/
|   ├── conf.d
|   └── ssl
└── apps/
    ├── another-app/
    └── nextjs-app/
        ├── app/
        |   ├── package.json
        |   ├── package-lock.json
        |   ├── public/
        |   ├── src/
        |   ├── next.config.js
        |   └── ...
        ├── Dockerfile
        └── .dockerignore

My docker-compose.yml:

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/ssl:/etc/nginx/ssl
    depends_on:
      - nextjs-app

  nextjs-app:
    build:
      context: ./apps/nextjs-app
      dockerfile: Dockerfile
    volumes:
      - ./apps/nextjs-app:/app
    ports:
      - "3000:3000"

My Nginx conf is as such:

server {
  listen 80;
  server_name mydomain.com;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

  # Redirect to HTTPS
  return 301 https://$host$request_uri/;
}

server {
  listen 443 ssl;
  server_name mydomain.com;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

  ssl_certificate /etc/nginx/ssl/mydomain.com.crt;
  ssl_certificate_key /etc/nginx/ssl/mydomain.com.key;

  location /myapp {
    proxy_pass http://nextjs-app:3000/myapp;
    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;
  }
}

My Dockerfile is similar to what the Documentation states for Docker, and it builds and runs correctly as standalone.

The next.config.js file has basePath and assetPrefix set such as:

const nextConfig = {
  basePath: '/myapp',
  assetPrefix: '/myapp',
  output: 'standalone',
  ...
};

module.exports = nextConfig;

Research

This question looks into the same kind of issue but it obviously doesn't work, as described previously.


Solution

  • Adding http2 support to my Nginx configuration solved the issue. If someone with more knowledge than me could explain why, I'd be pleased. My guess is that Next.js uses node http2 when being standalone which in return isn't compatible if nginx isn't using http2 resulting in a big mess.

    server {
      listen 443 ssl;
      http2 on;
      server_name mydomain.com;
      add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
      ssl_certificate /etc/nginx/ssl/mydomain.com.crt;
      ssl_certificate_key /etc/nginx/ssl/mydomain.com.key;
    
      location /myapp {
        proxy_pass http://nextjs-app:3000;
        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;
      }
    }