Lazy Panda

Developer

Posts
57
Comments
48
Likes
76
Posts
57
Comments
48
Likes
76
sidebar

Configure Multi site Multi Tenent application using NextJs

While working with NextJs and CMS like Drupal, AEM, Sitecore, etc as a headless approach, you might want to achieve multisite/multitenant capability on your nextjs frontend application. In short, you may need to see this on your browser. 

multisite multitenent nextjs solution

The Next.js Multisite includes multiple sites from a single NextJs application. It uses NextJs Middleware to serve the correct CMS base URL based on the incoming hostname. Let's see how we can build it step by step.

 

Step 1: Expose a REST API for domain mapping functionality

We should have a REST API endpoint for all frontend hostnames (domains) and associated CMS (backend) hostnames or domains. Like below - 

{
  "data": [
    {
      "frontend": "lazypandatech.com",
      "cms": "cms.blog.lazypandatech.com",
      "language": "en",
      "theme": "light"
    },
    {
      "frontend": "news.lazypandatech.com",
      "cms": "cms.news.lazypandatech.com",
      "language": "en",
      "theme": "light"
    },
    {
      "frontend": "trade.lazypandatech.com",
      "cms": "cms.trade.lazypandatech.com",
      "language": "en",
      "theme": "dark"
    }
  ]
}

 

 

Step 2: Create a middleware.ts file on your NextJs project

We need to have the middleware.ts file and from now on the application needs to run on the next server while serving the page, even in production as well. So this solution will only work for server-side rendering scenarios. For a static generation, this will create only one build at a time.

Considering, the application will execute on the next server and the application will follow the Server-side Rendering process. 

import { NextResponse, NextRequest } from 'next/server';

 

// all path details which we want to execute middleware

export const config = {

matcher: ['/', '/:id/:path']

};

 

function getBaseDomain(currentDomain: string | null) {

const domainAPI = `https://example.com/domain?hostname=${currentDomain}`;

 

try {

// fetch API and assuming you will receive the JSON like above, with filtered object

return fetch(domainAPI)

.then(res => res.json())

.then(response => {

return response.data;

});

} catch (error) {

// set default domain

return [

{

frontend: 'lazypandatech.com',

cms: 'cms.blog.lazypandatech.com',

language: 'en',

theme: 'light'

}

];

}

}

 

 

 

Now, update the middleware function, like below - 

export async function middleware(req: NextRequest): Promise<NextResponse | Response> {

const hostName = req.headers.get('host');

 

if (req.url.includes('favicon.ico')) return NextResponse.rewrite(req.nextUrl);

 

const domainUrl = hostName;

const domain = await getBaseDomain(domainUrl);

 

const response = NextResponse.next();

if (Array.isArray(domain)) {

response.headers.set('middleware-cms-doamin', domain?.[0]?.cms);

response.headers.set('middleware-language', domain?.[0]?.language);

response.headers.set('middleware-theme', domain?.[0]?.theme);

} else {

return NextResponse.redirect('/page-not-found');

}

 

return response;

}

 

 

Here, as you can see, I am calling the API based on the hostname received from the browser to get the respective backend domain, language, and theme value. Then set them on the response header, so I can get them into the NextJS application. 

In case I do not find the correct backend domain, it will redirect to a page not found or a 404 page.

 

Step 3: received the configurable data into your NextJS application

As of now from the middleware.ts file the response headers are being set, but we need to receive them and set the value while making an API call. So I wrote the logic inside the getInitialProps({ context }: any) method and from context I get the respective configurable data which were set in middleware response time. 

Let's see the code. 

_app.tsx file:

 

App.getInitialProps = async ({ Component, ctx }: any) => {

let cmsDomain = undefined;

let cmsLanguage = undefined;

let cmsTheme = undefined;

 

try {

cmsDomain = ctx?.res?._headers['middleware-cms-doamin'];

cmsLanguage = ctx?.res?._headers['middleware-language'];

cmsTheme = ctx?.res?._headers['middleware-theme'];

} catch (error) {

// set default in case any error

cmsDomain = 'cms.blog.lazypandatech.com';

cmsLanguage = 'en';

cmsTheme = 'light';

}

 

return {

pageProps: Component.getInitialProps ? await Component.getInitialProps(ctx) : {},

cmsDomain,

cmsLanguage,

cmsTheme

};

};

 

 

As, you can see the getInitialProps() method can get the configurable data from the context response header (_headers) and pass them to application props and now you have the base domain, language & theme value, which you can utilize them to make the API call and UI layer presentation. In order to persist the config value, you could leverage either simple local storage options in the App function or also can be saved it into context or redux as per your wish. 

I hope this article is useful for you, have a try and keep me posted in case of any queries. 

Happy Coding!