loading...

Deploy your NextJS Application on a different base path (i.e. not root)

efreyreg profile image Ernesto Freyre Originally published at Medium on ・6 min read

Photo by Skitterphoto from Pexels

One of NextJS default assumptions is that we will deploy our applications on the root base path of a domain. This is / . NextJS routing converts each file inside the pages folder to a matching path. So if we have a file named ./pages/index.js this matches / , for a file named ./pages/about.js it will be accessible at /about This is a pretty simple scheme, is basically how hyperlinks work. All you have to do to link both pages is:

import Link from 'next/link'

const Index = () =\> (
 \<div\>
 ...
 \<Link href='/about'\>\<a\>About Us\</a\>\</Link\>
 ...
 \</div\>
)

export default Index

For most applications this works right out of the box. Now, some applications do have the requirement to be served under a different base path than / Usually:

  • Application segmentation, several teams might be responsible for different parts of the application. Example: One team is responsible for the Dashboard (served at /dashboard) while other team owns the Sales Process (served at/sales)
  • Internationalization: An application default language is English, while moving to a new market team decided to add support for Spanish, translations where added and the Spanish version is deployed under the /es base path, now Spanish speaking users are redirected to /es/dashboard and /es/sales

NextJS Official documentation includes a section for Multi-Zones (https://nextjs.org/docs#multi-zones) this is the feature that allows NextJS applications to be served under a different base path. The most important part of this feature is using the assetPrefix setting on the next.config.js file.

The examples for multi-zone listed in the documentation all use Zeit’s Now cloud (https://zeit.co/home). But this is not a Now cloud exclusive feature. (perhaps this is not clear in the documentation)

To deploy a NextJS application under a different base path we need a reverse proxy that makes the mapping from whatever path we decide to serve our application to the correct URL. Of course having a reverse proxy for local development is not optimal. Although for academic purposes we will use NGINX to implement the 2 use cases we described above.

According to the documentation and the examples to run our application on a different base path we need to set the assetPrefix setting AND use the same base path on the Link’s as parameter. Since we don’t want to be rewriting the same code all over for every link, lets abstract that behavior on a custom Link component:

In the Application’s next.config.js file, add this:

module. **exports** = {
**assetPrefix** : **_process_**. **env**. **BASE\_PATH** || **''** ,
**...**
 **publicRuntimeConfig** : {
 ...
**basePath** : **_process_**. **env**. **BASE\_PATH || ''** ,
 ...
 },
}

To run our Application on a different base path we do:

$ BASE\_PATH=/sales yarn dev
[wait] starting the development server ...
[info] waiting on http://localhost:3000 ...
...

This also works for static exports or production builds:

$ yarn build

# Production build (SSR)
$ BASE\_PATH=/sales yarn start

# Static export
$ BASE\_PATH=/sales yarn export
$ cd out
$ ws -p 3000

If we do this on development and try to access http://localhost:3000 our application won’t completely work.

All resources failing to resolve while accessing http://localhost:3000

All application’s resources (JS, CSS, Images) will be prefixed with the /sales base path. Without a reverse proxy to do the right mapping it won’t work.

Installing and Configuring a local NGINX Reverse Proxy.

There are several ways you can locally install and configure a NGINX reverse proxy. My preferred way is to use Kong (https://konghq.com/) via a NPM package I put together to manage it from the CLI. https://www.npmjs.com/package/dev-kong. (The only dependency is having docker locally installed, since this package depends on it to run a dockerized kong instance)

$ npm install -g dev-kong
$ kong --version
0.8.2

$ kong start
Starting Kong
Creating network "t\_default" with the default driver

Creating t\_kong-database\_1 ...
Creating t\_kong-database\_1 ... done

Creating t\_kong\_1 ...
Creating t\_kong\_1 ... done

Once started we have a local NGINX reverse proxy we can control with a CLI.

Accessing localhost on the browser will give you:

Nothing configured yet.

We also need a fake or local domain to resolve to the loopback IP address (usually 127.0. 0.1). Most simple way to do this is to add the domain (I picked for my tests: outsrc.local) to the /etc/hosts file.

$ sudo sh -c 'echo "127.0.0.1 outsrc.local" \>\> /etc/hosts'

# Check it
$ cat /etc/hosts
...
...
...
127.0.0.1 outsrc.local

And finally the mapping on NGINX:

# First get local network IP address (Mac OS only)
$ ipconfig getifaddr en0
172.20.10.2

$ kong add --stripuri sales outsrc.local http://172.20.10.2:3000 /sales
┌──────────────────────────┬──────────────────────────────────────┐
│ http\_if\_terminated │ true │
├──────────────────────────┼──────────────────────────────────────┤
│ id │ 775a9dc2-4b86-4258-82c8-4f2913f5a219 │
├──────────────────────────┼──────────────────────────────────────┤
│ retries │ 5 │
├──────────────────────────┼──────────────────────────────────────┤
│ preserve\_host │ false │
├──────────────────────────┼──────────────────────────────────────┤
│ created\_at │ 1575559214000 │
├──────────────────────────┼──────────────────────────────────────┤
│ upstream\_connect\_timeout │ 60000 │
├──────────────────────────┼──────────────────────────────────────┤
│ upstream\_url │ http://172.20.10.2:3000 │
├──────────────────────────┼──────────────────────────────────────┤
│ upstream\_read\_timeout │ 60000 │
├──────────────────────────┼──────────────────────────────────────┤
│ upstream\_send\_timeout │ 60000 │
├──────────────────────────┼──────────────────────────────────────┤
│ https\_only │ false │
├──────────────────────────┼──────────────────────────────────────┤
│ strip\_uri │ true │
├──────────────────────────┼──────────────────────────────────────┤
│ uris │ /sales │
├──────────────────────────┼──────────────────────────────────────┤
│ name │ sales │
├──────────────────────────┼──────────────────────────────────────┤
│ hosts │ outsrc.local │
└──────────────────────────┴──────────────────────────────────────┘

Show mapped paths:

$ kong list

kong list output

Above table reads: One endpoint named: sales when accessing outsrc.local/sales route it to http://172.20.10.2:3000 and for all requests remove the /sales prefix.

(We need to use local network IP because our NGINX instance is running inside a docker container and our frontend application is running on the host)

Any number of path mappings can be added. Lets add one for the dashboard application we will run on a different port:

$ BASE\_PATH=/dashboard yarn dev --port 3010
[wait] starting the development server ...
[info] waiting on http://localhost:3010 ...
...

And the mapping:

$ kong add --stripuri dashboard outsrc.local http://172.20.10.2:3010 /dashboard
...

Running kong list again we get:

sales and dashboard apps running on different ports and different base paths.

Demo time. Multiple Apps different base paths

If you follow the previous steps, you already have a local domain pointing to 127.0.0.1, NGINX installed and running. We need an Application.

Lets clone a repo with an Application (already prepared) twice:

$ git clone --branch efg/custom-name git@github.com:outsrc/template-frontend.git dashboard-app

$ git clone --branch efg/custom-name git@github.com:outsrc/template-frontend.git sales-app

Install dependencies yarn install and run each application specifying APP_NAME and BASE_PATH

$ APP\_NAME=Dashboard BASE\_PATH=/dashboard yarn dev --port 3010

$ APP\_NAME=Sales BASE\_PATH=/sales yarn dev --port 3000

Our two mappings are the same so I won’t repeat them here.

On the browser we get:

Dashboard application on /dashboard

Sales application on /sales

Done! We have two NextJS applications running side by side on the same domain, different base paths.

Demo time. Same application Spanish Translation

First lets clear the current path mappings we have on NGINX

$ kong delete sales
Deleted

$ kong delete dashboard
Deleted

Clone the code branch with Internationalization and the Spanish translation:

$ git clone --branch efg/with-intl git@github.com:outsrc/template-frontend.git spanish-app
$ cd spanish-app
$ yarn install
...

$ LOCALE=es BASE\_PATH=/es yarn dev --port 3010

This will start the Application with the Spanish localization on base path /es

Mapping the path on NGINX:

$ kong add --stripuri spanish outsrc.local http://172.20.10.2:3010 /es

Spanish translation mapped to /es

We get this on the browser:

Our Spanish translated application served on /es

I intentionally leaved out some important pieces in terms of Internationalization. Like, detecting users browser preferences so we can redirect them to the right path.

Conclusions.

  • NextJS DOES support deploying applications on different base paths other than the root base path.
  • Combination of assetPrefix and Link as parameter.
  • Deploy to a different base path is not a developing time task. Is an SRE task. Meaning, Frontend developers should not be focused too much where the applications are getting deployed (base path) only be ready to support it. Local development should always use root path.
  • Works on static exports.
  • Prefer to use runtime configuration (https://nextjs.org/docs#runtime-configuration) over build time configuration (https://nextjs.org/docs#build-time-configuration)
  • If you really need to use NGINX locally, I recommend you yo use Kong (via dev-kong NPM package)

Posted on Dec 6 '19 by:

efreyreg profile

Ernesto Freyre

@efreyreg

Software Engineer, Father of Zeki and Frances. Working at cyber-security and building dev tools in free time.

Discussion

markdown guide