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.
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.
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;
This question looks into the same kind of issue but it obviously doesn't work, as described previously.
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;
}
}