NextJS app deploying using Docker and NGINX so I can have a custom base path

2020-03-25 docker nginx next.js

I have created an application that is working perfectly locally and now I need to deploy on a server with a custom base path. My app is being deployed to a Docker Container where I have a second Docker Container with NGINX doing reverse proxy.

I have seen lots of blogs/queries/solutions online and after 3 weeks i am still no closer in getting anything to work. I have tried using "Express" wrapping my app but I encountered blocking issues

I am using the information in the following blog as to how to use assetPrefix :

My project structure

|- .next
|- client
     |- polyfills.js
|- components
     |- Link.tsx
     |- etc
|- node-modules
     |- ...
|- pages
     |- index.js
     |- page1.js
     |- page2.js
     |- etc
|- public
     |- appIcon.png
|- scripts
     |- utils.js
|- stylesheets
     |- main.css
|- next.config.js
|- package-lock.json
|- package.json

Example of how static images are being used in my app

<img className="my-images" src="/appIcon.png" alt="App Icon"/>

Example of how style sheet is being used

import "../stylesheets/main.css";

"next.config.cs"

const withCSS = require("@zeit/next-css");
module.exports = withCSS();

module.exports = withCSS({
    assetPrefix: process.env.BASE_PATH || '',

    publicRuntimeConfig: {
        basePath: process.env.BASE_PATH || '',
    },

    webpack: function(cfg) {
        const originalEntry = cfg.entry
        cfg.entry = async () => {
            const entries = await originalEntry()
            if (entries['main.js'] &&
                !entries['main.js'].includes('./client/polyfills.js')
            ) {
                entries['main.js'].unshift('./client/polyfills.js')
            }
            return entries
        }
        return cfg
    },
});    

"DockerFile"

...    
COPY package*.json ./
...
RUN --mount=type=ssh npm install
...
COPY .next .next/
COPY next.config.js  ./
...
ENV BASE_PATH=a/b 
CMD [ "./node_modules/.bin/next", "start", "-p", "8080"]

my NGINX rule is

location = /a/b/ {
    set $upstream http://mysite:8080/;
    proxy_pass $upstream;
}
location /a/b/ {  
    set $upstream http://mysite:8080/;
    proxy_pass $upstream;
}

It is my understanding that these rules means that a url

Link.tsx : overwriting default links to have the basePath as the prefix

import NextLink, { LinkProps } from 'next/link'
import { format } from 'url'
import getConfig from 'next/config'

const { publicRuntimeConfig } = getConfig()

const Link: React.FunctionComponent<LinkProps> = ({ children, ...props }) => (
    <NextLink
        {...props}
        as={`${publicRuntimeConfig.basePath || ''}${format(props.href)}`}
    >
        {children}
    </NextLink>
)

export default Link

Example of how i am using the link

import Link from "./Link.tsx";
....
<Link href="/page1">  

ISSUE 1 : assets do not appear to be loading

I can access my app with the following : http://host/a/b/

However NONE of the static files are loaded, i.e. "appIcon.png" is not found neither is the stylesheet "main.css"

ISSUE 2 : pages other than "index" are not accessible

When I try and access any other page using the URL it does not work, the index page is always shown

For example

"http://host/a/b/page1" 

should show page1, but it does not. The index page is shown. It is my understanding that the NGINX rules would remove ONLY the /a/b/ part therefore it should call through to

"http://host/page1" 

and show the page1.

Why is this not working?

Can anyone please advise me how to get this all working.

Answers

See the answer in this post which has a comprehensive explanation of the solution: https://stackoverflow.com/a/60952373/2663916

A summary is below.

For Issue 1 : public images

I used "next-images" package so that in my components i could import the image and reference the imported image.

next.config.js

const withCSS = require("@zeit/next-css");
const withImages = require('next-images');

module.exports = withCSS(withImages({
   ....
}));

In my component import the images

import img1 from '../public/image_name1.png'
import img2 from '../public/image_name2.png'

reference the imported object in the src attribute

<img src={img1} alt="image_name1"/>

For Issue 1 : stylesheets

When i set the assetPrefix in "next.config.js" correctly and had the value set in the environment when i built and run my application, and have the reverse proxy using this path then the stylesheets are picked up correctly.

See the linked stackoverflow answer

For Issue 2

This appears to be an NGINX configuration.

When using a variable in the location you have to make sure you pass on the paths for example

location ~ /a/b/(.*)$ {  
    set $upstream http://myhost:8080/$1;
    proxy_pass $upstream;
}

or don't use a variable at all

location /a/b/ {
    proxy_pass http://myhost:8080/;
}

Related