Next.js Discord

Discord Forum

Multiple Layouts Same Route

Answered
necm1 posted this in #help-forum
Open in Discord
Hi!

I'm trying to create a multi-tenant application where users have the possibility to choose between multiple layouts & landings inside a nx monorepo.

Actually my question is: Can I use multiple layouts for the same route, but choose / select them depending on the configuration?

basically, it could look like this
/app
  layout.tsx <- Root
    (template1)
       layout.tsx <- Selectable layout for domain
       /overview
         page.tsx
    (template2) <- Selectable layout for domain
       layout.tsx
       /overview
         page.tsx


Currently I've a provider, which imports all layouts and in my layout I simply do a request to my API, which returns the selected template (for example template1) and in my BaseLayout-Component file I just try to fetch the information and return the selected Layout.

I guess this could leak to a performance issue, due to importing all layouts in my provider and try to get the correct one, based on the returned string from my API.

It's working, but dunno if it could be a issue in the future by adding more templates.
Answered by Ray
maybe you could try this with parallel routes?
export default async function Layout({
  template1,
  template2,
}: {
  template1: React.ReactNode;
  template2: React.ReactNode;
}) {
  const template = await fetchTemplate();
  switch (template) {
    case "a":
      return template1;
    case "b":
      return template2;
    default:
      <div>default layout</div>;
  }
}
View full answer

61 Replies

Answer
just a little bit confused, because the docs says: render a @dashboard or @login route depending on the authentication state.
so it should basically be an route and not a layout
🤔
@necm1 so it should basically be an route and not a layout
oh so just the layout.tsx is different?
actually everything would be kinda different
from the UI / UX perspective
but they contain the same functions
maybe better to do it in a component
@Ray maybe better to do it in a component
thats what I've now
bascially every page or even the main layout have a BaseLayout / BasePage
which gets the template name passed into it from the TemplateProvider (which received the data from the RootLayout by fetching the domain information and passes it into the Provider Context)
and I store the Layout including the Pages into an object like this:
const templates = [
  "template-1": {
     Layout: TemplateOneLayout,
      OverviewPage: TemplateOneOverviewPage
    },
   .....
]
but dunno if this could affect my perfomance over the while
/app
layout.tsx <- Root including your code
@template1
layout.tsx <- Selectable layout for domain
/overview
page.tsx
@template2
layout.tsx
/overview
page.tsx
what do you mean?
So, the docs say that the parallel routing in this particular case would use the route (alias page.tsx) based on a condition like isAuthenticated
you could have layout.tsx inside the parallel route
yes
I would suggest you init a new project and play with it
and based on the layout params when I return template1 (which points to @template1) it would prolly use the page inside @template1/overview/page.tsx, right?
@Ray I would suggest you init a new project and play with it
definitely going to do that, just wanted to make things clear, to understand it :D
you could also have different url structure for different parallel route
eg, only @template2 have /only-template2 this url
@Ray yes if the current url is /overview then `@template1/overview/page.tsx` will render
but I could go the same way like this:
/app
  layout.tsx <- Root including your code
    @template1
       layout.tsx <- Selectable layout for domain
       /overview
         page.tsx
    @template2
       layout.tsx
       /overview
         page.tsx
   /overview <- is in app folder
     layout.tsx <- main overview route, which the layout have some SSR fetching and render the page, based on the given template
right?
cuz every route have the same api fetchings, but only the layout / components gonna change
but will use the same fetched data
if so, you have to create the parallel route in a route group like this
/app
layout.tsx <- Root including your code
(template)
layout.tsx <- parallet layout
@template1
layout.tsx <- Selectable layout for domain
@template2
layout.tsx\
/overview
or you try the parallel route in a new project first since I still not 100% understand how you gonna make the app:lolsob:
alright, thank you. I'll test it now and give some feedback afterwards 😭
yeah ask here if you are facing issue
thanks for your time. I'll do that! :D
export default async function RootLayout({
  template1,
}: {
  template1: React.ReactNode;
}) {
  const template = await getDomain(); // returns template-1
  const templates: { [key: string]: React.ReactNode } = {
    'template-1': template1,
  };

  console.log('template', template, 'react node', templates[template]); // output: template template-1, react node: undefined

  return templates[template] || <h2>not found</h2>;
}
@necm1 tried this one now, but the given template params in the layout seems to be undefined
sometime, It might need to remove .next and restart the server
oh, gonna try that one
@Ray sometime, It might need to remove `.next` and restart the server
worked perfectly. Thank you very much
Elm sawfly
@necm1 I have the same need for a project I am working on. Any chance you could share your solution on a gist or something?
@necm1 sure. just give me some time I’ve a meeting now
Elm sawfly
thanks man. really looking forward to this
@Elm sawfly thanks man. really looking forward to this
sorry, currently at the gym right now but after that I'll post the solution here
Elm sawfly
hey mate - any chance you can post that sometime tonight? if you aren't comfortable with sharing it, I'll understand and move forward without. but would really appreciate the assist otherwise 🙂
thats basically my structure
(panel)/layout.tsx:
export async function getDomain() {
  const protocol = headers().get('x-forwarded-proto');
  const host = headers().get('host');
  const url = `${protocol}://${
    host?.includes(':') ? host.split(':')[0] : host
  }`;

  const res = await POST('domain-manager/info', {
    body: { url },
  });

  if (res.status && res.status === 403) {
    return [];
  }

  return (await res.json()).data.template;
}

export default async function PanelLayout({
  template1,
}: {
  template1: React.ReactNode;
}) {
  const template = await getDomain();
  const templates: { [key: string]: React.ReactNode } = {
    'template-1': template1,
  };

  return <Fragment>{templates[template] || <h2>not found panel</h2>}</Fragment>;
}
(panel)/@template1/layout.tsx
export default async function TemplateOneLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html className={inter.className}>
      <body>
        <Layout>{children}</Layout>
        <Toaster
          toastOptions={{
            classNames: {
              toast: 'group-[.toaster]:border-none',
            },
          }}
        />
      </body>
    </html>
  );
}
and inside the template1/layout.tsx I simply use the components for template1 like the
Elm sawfly
this worked out for me - thanks so much!