NextJS deploy to a specific URL path

21,892

Solution 1

I found a solution using NGINX to reverse proxy a URL with the base path.

Useful links

Application Changes

Dependencies

  • next-images : in order to import static images from "public" when using a reverse proxy
  • @zeit/next-css : in order to use stylesheet files
  • as well as usual NextJS dependencies

next.config.js

Add a "next.config.js" file at the root of your application so that you can specify the "assetPrefix" and "publicRuntimeConfig.basePath"

  • assetPrefix : used by NextJS when accessing components, stylesheets, pages etc
  • publicRuntimeConfig.basePath : used in s so specify the prefix to add to the link, used in "src" tags of "" elements when using public images

Example

const isProd = process.env.NODE_ENV === 'production'

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

/*
 * Gets the BASE_PATH from the command used to start this app.
 * If BASE_PATH is specified but it does not start with a "/" 
 * then add it. 
 */
function getBasePath() {
    var basePath = ''

    if (isProd && process.env.BASE_PATH){
        if (process.env.BASE_PATH.startsWith("/") ){
            basePath = process.env.BASE_PATH;
        } else {
            basePath = "/" + process.env.BASE_PATH;
        }
    } 

    console.log("getBasePath() : isProd = " + isProd);
    console.log("getBasePath() : basePath = " + basePath);

    return basePath
}

module.exports = withCSS(withImages({
    assetPrefix: getBasePath() ,

    publicRuntimeConfig: {
        basePath: getBasePath() ,
    },

}));

Static images

Use "next-images" in order to import the images and reference the imported object in the 's src tags

Change any references to your static images (those in /public folder) to have the base path prefix. For example my "Footer" component has the following

import '../stylesheets/main.css';

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

export default class o extends React.Component {

    render(){
        var prefix = publicRuntimeConfig.basePath
        return  (
            <div >
                <a className="icon" href="http://www.image_name.com" >
                    <img src={img1} alt="image_name1"/>
                </a>
                <a className="icon" href="http://www.image_name2.com">
                    <img  src={img1} alt="image_name2"/>
                </a>
            </div>
        );
    }
}

Note: I tried to use the publicRuntimeConfig.basePath as a prefix to the src URL (as below), but this did not work in my deployed environment (see below)

    import getConfig from 'next/config'
    const { publicRuntimeConfig } = getConfig()
    ...
    ...
    <a className="icon" href="http://www.image_name.com" >
        <img src={`${publicRuntimeConfig.basePath}/image_name1.png`} alt="image_name1"/>
    </a>

Links

Change your Link's to use the base path prefix, for example in my "Header" component i have the following

import Link from 'next/link';
import '../stylesheets/main.css';

import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()

const detailId1 = "banana"

const Header = () => (
    <div>
        <div>
            <Link href={`${publicRuntimeConfig.basePath || ''}/`}>
                <a className="linkStyle">Home</a>
            </Link>

            <Link href={`${publicRuntimeConfig.basePath || ''}/about`} >
                <a className="linkStyle">About</a>
            </Link>

            <Link href={`${publicRuntimeConfig.basePath || ''}/details/[id]`} 
                  as= {`${publicRuntimeConfig.basePath || ''}/details/${detailId1}`} >
                <a className="linkStyle">Details Var 1</a>
            </Link>
        </div>
  </div>
);

export default Header;

Note: In the blog https://levelup.gitconnected.com/deploy-your-nextjs-application-on-a-different-base-path-i-e-not-root-1c4d210cce8a, it contains a "Link.tsx" that does the adding of the prefix for you, so you simply use that Link component (import Link from "./Link.tsx";) and not the nextJS version (import Link from 'next/link';). However, that "Link.tsx" does not work for me when I have variables in my link URLs.

Running your nextjs app

When running your application locally when you do NOT want a base path you can just running

npm run dev

As no BASE_PATH is specified your application should be accessible from "http://localhost:3000" and your src values should be "/image_name1.png" and when you hover over your s you will see the link is "http://localhost:3000/pagename"

When you want to run with a base path do the following

export BASE_PATH=a/b
npm run dev

Note: for some reason in my environment if i specify "export BASE_PATH=/a/b" (/ at the start of the path) I get a folder directory added to the beginning of the path. Therefore i specify it without the starting / and the code in next.config.js adds the starting / if need be.

You can not access your app at "http://localhost:3000" as you have the base path/assetPrefix/publicRuntimeConfig.basePath set. Now you need a reverse proxy.

NGINX : Reverse Proxy

I found the easiest setup was to use a NGINX docker image. You need to run NGINX with a configuration containing the redirection to your NextJS app.

Create a folder and add in that folder a "default.conf" file. Make sure the path you put in your "location" is the SAME path you specified for BASE_PATH when starting your nextjs app.

server {
    listen 80;
    server_name  localhost;

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

Important Notes:

  • you have to have the trailing / on the end of your proxy_pass URL otherwise additional paths are not passed onto your NextJS apoplication
  • if you use a variable in the location you must make sure you include passing on the paths

example

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

In a command prompt from that directory run a NGINX docker image, telling it to use your config file.

docker run --name mynginx1 -v C:/zNGINX/testnginx/conf:/etc/nginx/conf.d -p 80:80 -d nginx
  • name of the docker container is "mynginx1"
  • the v parameter is telling it to copy any files in "C:/zNGINX/testnginx/conf" on your computer to the "/etc/nginx/conf.d" directory in the docker container. This will copy your "default.conf" to the docker container and NGINX will read that configuration file.
  • Note: Make sure you have the "conf.d" in your path for the docker location (":/etc/nginx/conf.d"), blogs I read did not include this part, it only specified ":/etc/nginx/", and without it the image doesn't start.
  • the p parameter is telling to run NGINX on port 80

Go to the following URL

http://localhost:80/a/b/

NGINX will redirect that URL to "http://localhost:3000". Therefore your application should now be accessible from the URL with the base path. Clicking on s should work, the link should contain the base path which goes to NGINX which redirects back to the application stripping off the base path leaving any other paths.

Real World Server Deployment using Docker

If you are deploying your application to a server, as apposed to running locally, you can build your application and then copy the relevant files/folders to the server machine. Make sure you have the BASE_PATH set when both building and running your app

export BASE_PATH=a/b
npm run build

cp package*.json [server_location]
cp next.config.js [server_location]
cp ./next [server_location]

then on that server location run

npm install
export BASE_PATH=a/b
npm run start   

Note: If you have images in "public" that you reference in your app, use "next-images" and import the image rather than use the publicRuntimeConfig.basePath as a prefix. When i did the latter the images were not found. See the section about about images for examples.

Solution 2

In Next.js ≥ 9.5, you can set a basePath in your next.config.js. For example, if you want your entire Next.js app to live at /docs, you can use:

// next.config.js
module.exports = {
  basePath: '/docs'
}

Next will will make sure all assets are served from the right place, and it will automatically prefix this base path for all Links in your app, as well as during programmatic navigation using next/router. For example,

<Link href="/about">
  <a>About Page</a>
</Link>

will be transformed to link to /docs/about, as will

router.push('/about')

This means that you can change basePath without changing anything at all in the actual code of your app.

Solution 3

To add to the answers here, simply using basePath is not enough. basePath works very well for automatically pointing links, but it does not do anything to static files served from public directory.

For example you have public/img/my-img.png that you referred in your img or Image element as <img src="img/my-img.png" /> or <Image src="/img/my-img.png" />, you have to change it to <img src="your-sub-path/img/my-img.png" /> or <Image src="/your-sub-path/img/my-img.png" /> respectively.

Solution 4

You can use custom server to create NextJS application work on your specific URL:

See the example here:

https://github.com/zeit/next.js/tree/canary/examples/custom-server-express

The key point is to add your specific url as an API, then forward user's request to your specific page you want to serve:

const express = require('express')
const next = require('next')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = express()

  server.get('/my-test-application/path-for-my-app', (req, res) => {
    return app.render(req, res, '/index', req.query)
  })

  server.get('/my-test-application/path-for-my-app/page1', (req, res) => {
    return app.render(req, res, '/page1', req.query)
  })

  server.get('/posts/:id', (req, res) => {
    return app.render(req, res, '/posts', { id: req.params.id })
  })

  server.all('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(port, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${port}`)
  })
})

Solution 5

To me, all the solutions mentioned is too much trouble for me, so i rolled the following way.

next.config.js

module.exports = {
  basePath: '/drh',
}

_app

You can overwrite the _app.js file.

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} base="/drh" />
}

This way all pages will have a prop base hard coded.

export default function Index({ base }) {
  return <img src={`${base}/images/hooks-logo.png`}/>
}

context

if you don't want to push this base to all children using a prop, this is the place you can use a context. The context provider can be setup in the _app.js as well.

seems to me one of a good entry point to nextjs app is _app.js, verse a typical react app is index.js.

Share:
21,892
se22as
Author by

se22as

Updated on July 09, 2022

Comments

  • se22as
    se22as almost 2 years

    I am working on my first NextJS application. When I run "npm run dev" or "npm run start" it deploys my application to

    http://host:port/
    

    When I navigate to a page the url becomes

    http://host:port/page1
    

    I need to have my own specific URL, such as

    http://host:port/my-test-application/path-for-my-app/
    http://host:port/my-test-application/path-for-my-app/page1
    

    Furthermore, my app has a lot of elements to link to other areas of the applications, i need these to also go to URL with the basePath and not just go to the root path.

    I will also be depolying this app to different servers which will have different basePaths, therefore this can not be hardcoded in my app.

    How can I do this?

    With other applications such as react/vue/angular/native JS, I simply build my application and put the build code in a "my-test-application/path-for-my-app" folder on my server.

    I tried this with my NextJS application but i got an error that ".next" folder could not be found.

    I googled and could find some references to using "assetPrefix" or using "Zones". However I do not really understand what I am supposed to do.

    How do i get my app deployed to specific URL

    Solution 1: Restructure "pages" - Does not enable me to deploy to different servers with different basePaths

    I could create the folder structure inside my "pages" directory and change all my elements to use this folder structure.

    |- pages
         |- my-test-application
               |- path-for-my-app
                    |- index.js
                    |- page1.js
    
    
    <Link href="/my-test-application/path-for-my-app/page1" >
    

    I dislike this solution as the basePath is hardcoded into my application, as to apposed to a deployment setting.

    If I wanted to deploy my app on 2 servers with different basePaths (i.e. below) I would have to have 2 versions of the code.

    http://host:port/my-test-application_1/path-for-my-app/page1
    http://host:port/my-test-application_2/diff-path-for-my-app/page1
    

    Updated: I have updated this question on 5th March to include my need for s to work and one solution which I do not like.