How To Run Multiple Next.js Apps On Same Domain

Arihant Verma
INDmoney
Published in
4 min readJan 19, 2022

--

Next.js Logo

Problem

If you have to use multiple Next.js apps on a single domain, the only straightforward way for you to do that is to have baseUrl option set in next.config.js. But the problem with that is, you'll have different URLs for different apps on your domain:

example.com/app-1/some-route
example.com/app-2/some-other-route

If you want to do have them like so –

example.com/some-route ( from app-1 )
example.com/some-other-route ( from app-2 )

you’re a little out of luck. My first hunch was that it’d be possible to use the baseUrl, but change the anchor links from

/app-1/some-route-on-some-page
/app-2/some-route-on-some-page

to

some-route-on-some-page
some-route-on-some-page

by using the as property of next/link by masking the URLs that users will see, while still being able to request the correct base-path-added url from next server. As much as I could google, it's not possible. If you've made it work, please let me know on twitter, I'd be very grateful 🌻.

I ended up making it work by doing a bunch of things.

Workaround

Using asset prefix to make a distinguishing factor between assets of different next.js apps, along with a next.js re write rule.

// next.config.jsimport { BASE_PREFIX_FOR_APP } from '@/common/constants.js'module.exports = {
assetPrefix: BASE_PREFIX_FOR_APP,
async rewrites(){
return [{
source: `${BASE_PREFIX_FOR_APP}/_next/:path*`,
destination: '_next/:path*'
}]
}
}

With this, the client will request assets from ${BASE_PREFIX_FOR_APP}/_next/:path*, but it'll reach your app at a path that it serves assets from /_next/:path* ( /_next/static/* to be more precise ).

In a similar fashion, you’d handle images and api request paths

// next.config.jsmodule.exports = {
assetPrefix: BASE_PREFIX_FOR_APP,
async rewrites(){
return [
{
/** ASSET PREFIX */
source: `${BASE_PREFIX_FOR_APP}/_next/:path*`,
destination: '/_next/:path*'
},
{
/** IMAGE PREFIX */
source: `${BASE_PREFIX_FOR_APP}/images/:query*`,
destination: '/_next/image/:query*'
},
/** API PREFIX */
{
source: `${BASE_PREFIX_FOR_APP}/api/:path*`,
destination: '/api/:path*'
}

]
}
}

For images you’ll have to wrap next/image component in your own component, to request your images with the prefix BASE_PREFIX_FOR_APP, using a custom Next.js image loader

// CustomImage.tsximport Image from 'next/image'const CustomImage: typeof Image = props => {
const finalProps = {
props,
loader({ src, width, quality } {
const urlQuery = `?url=/images${src}`
return `/${BASE_PREFIX_FOR_APP}/images${urlQuery}&w=${width}&q=${quality ?? 75}`
})
}
return <Image {...finalProps} />
}
export default CustomImage;

So far so good. Doing all these steps solves most of the problems, except one.

The Last Samurai ⚔️

When you use a next/link to navigate to another route, and that upcoming route has a getServerSideProps method implemented, Next.js will send an API request to the server, which will run getServerSideProps and return a JSON containing the result. You can read about this in Next.js docs here. That resulted JSON fetch request data is used to render the upcoming route. Those data fetch requests have a path that looks like this: _next/data/<build-id>/<route-slug>.json.

The problem with that for our context — to be able to run multiple Next.js apps on same domain without base url prefix — is that Next.js doesn’t give us a way to control this path. Which is to say there’s no data fetch request path URL prefix which Next.js gives as a configuration option. Because of that we have a hard time finding a distinguishing factor for data URLs for multiple apps.

The fix we ended up using is as follows:

Remember that the data fetch url looks like _next/data/<build-id>/<route_slug>.json. If we could have a way generate unique <build-id> for all our apps, we could use that and write a re write rule at load balancer level to distinguish the request between multiple Next.js apps. By default the <build-id> is a random id generated by Next.js build process for each build. Thankfully they give us a way to control the build id.

We ended up using a package called next-build-id to configure a custom build id where we gave a app-1-<git-commitSHA> signature and added a re write rule in our load balancer for incoming request host name having app-1 for first app, app-2 for second app and so on and so forth, to solve this.

That’s it! So if —

Photo by Austin Chan on Unsplash

this article should have helped you :)

--

--

I write poetry and short fiction. I meditate, code, dance, sing, play 🏀, clean stuff. I’m a non sticky pan to events 🍳.