ReactHustle

Ultimate Guide: How to Setup Multiple Layouts in NextJS (Typescript)

Jasser Mark Arioste

Jasser Mark Arioste

Ultimate Guide: How to Setup Multiple Layouts in NextJS (Typescript)

Hello, hustlers! In this tutorial, you'll learn how to implement multiple and different layouts in different parts of an application in a NextJS project along with Typescript.

This guide is specifically for apps using the /pages directory. If you're using Next 13 and you're using the /app directory,  there's already documentation by the NextJS team on how to achieve that.

Objectives of this Tutorial #

  1. We should be able to use different layouts in different parts of the application. For example, admin pages can have a different layout than pages like about, contact, and home.
  2. We should be able to define layouts and know what layouts are available. This is the main purpose of why we're using Typescript. If we use a layout that does not exist it should give an error.
  3. It should be possible for a page to have no layout.
  4. We'll use DaisyUI + Tailwind CSS for styling the navbar. 

Step 0 - Project Setup #

To follow along with this tutorial, I created a NextJS starter template on GitHub that uses daisyUI + TailwindCSS.

First, create a new NextJS Project by running the command:

npx create-next-app next-layout-tutorial -e https://github.com/jmarioste/next-multiple-layouts-tutorial

Next, run the following commands to start the local development server on localhost:3000:

cd next-layout-tutorial && yarn dev

Step 1 - Creating Different Layouts #

A Layout is simply a wrapper component for pages in our application. Let's create two layout components, one for the frontend and one for /admin pages. 

First, create the file components/common/MainLayout.tsx

// components/common/MainLayout.tsx
import React, { PropsWithChildren } from "react";
import Navbar from "./Navbar";
const MainLayout = ({ children }: PropsWithChildren) => {
  return (
    <>
      <Navbar />
      <main>{children}</main>
    </>
  );
};
export default MainLayout;
123456789101112

Next, create the file components/common/AdminLayout.tsx

// components/common/AdminLayout.tsx
import React, { PropsWithChildren } from "react";
import AdminNavbar from "./AdminNavbar";
const AdminLayout = (props: PropsWithChildren) => {
  return (
    <>
      <AdminNavbar />
      {props.children}
    </>
  );
};
export default AdminLayout;
123456789101112

The difference between the two layouts is that they have different navbars. Let's create the two Navbar components also. Note here that I'm using daisyUI + tailwind classes. You can also use a different UI library if you'd like. 

Create the file components/common/Navbar.tsx

// components/common/Navbar.tsx
import React from "react";
import Link from "next/link";
const Navbar = () => {
  return (
    <div className="navbar bg-base-100">
      <div className="flex-1">
        <a className="btn btn-ghost normal-case text-xl">My Website</a>
      </div>
      <div className="flex-none">
        <ul className="menu menu-horizontal px-1">
          <li>
            <Link href="/">Home</Link>
          </li>
          <li>
            <Link href="/admin">Admin</Link>
          </li>
          <li>
            <Link href="/contact">Contact</Link>
          </li>
          <li>
            <Link href="/about">About</Link>
          </li>
        </ul>
      </div>
    </div>
  );
};
export default Navbar;
1234567891011121314151617181920212223242526272829

Next, let's create the AdminNavbar component. Create the file components/common/AdminNavbar.tsx:

// components/common/AdminNavbar.tsx
import Link from "next/link";
import React from "react";
const AdminNavbar = () => {
  return (
    <div className="navbar bg-base-300">
      <div className="flex-1">
        <a className="btn btn-ghost normal-case text-xl">Admin</a>
      </div>
      <div className="flex-none">
        <ul className="menu menu-horizontal px-1">
          <li>
            <Link href="/admin">Dashboard</Link>
          </li>
          <li>
            <Link href="/admin/users">Users</Link>
          </li>
          <li>
            <Link href="/">Back to Website</Link>
          </li>
        </ul>
      </div>
    </div>
  );
};
export default AdminNavbar;
1234567891011121314151617181920212223242526

Step 2 - Creating a Layout Map #

Next, we'll create a layout map to know which layouts are available in our application. If you have many different pages with different layouts, having them defined in one place is super helpful.

Create the file components/common/Layouts.tsx.

// components/common/Layouts.tsx
import AdminLayout from "./AdminLayout";
import MainLayout from "./MainLayout";
export const Layouts = {
  Main: MainLayout,
  Admin: AdminLayout,
};
export type LayoutKeys = keyof typeof Layouts; // "Main" | "Admin"
12345678

Explanation:

Later, we'll use LayoutKeys so that we can only use "Main" and "Admin" values when specifying the layout of a Page. Any values other than these will result in an Error.

Step 3 - Implementing Typescript Support #

Next, We'll implement Typescript support. First, create the file components/common/types.tsx. We'll use this file for all common types in our app.

Let's modify NextPage and add the Layout property:

// components/common/types.tsx
import { NextPage } from "next";
import { LayoutKeys } from "./Layouts";
export type MyPage<P = {}, IP = P> = NextPage<P, IP> & {
  Layout?: LayoutKeys;
};
123456

Next, let's modify AppProps.Component and add the Layout property.

import { NextComponentType, NextPage, NextPageContext } from "next";
import { AppProps } from "next/app";
import { LayoutKeys } from "./Layouts";
export type MyPage<P = {}, IP = P> = NextPage<P, IP> & {
  Layout?: LayoutKeys;
};
export type MyAppProps = AppProps & {
  Component: NextComponentType<NextPageContext, any, any> & {
    Layout: LayoutKeys;
  };
};
1234567891011

Step 4 - Modifying _app.tsx #

Next, let's modify pages/_app.tsx to get the correct layout from a specific page:

import "../styles/globals.css";
import { MyAppProps } from "components/common/types";
import { Layouts } from "components/common/Layouts";
function MyApp({ Component, pageProps }: MyAppProps) {
  const Layout = Layouts[Component.Layout] ?? ((page) => page);
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}
export default MyApp;
123456789101112

Explanation:

Line 4: MyApp component now uses MyAppProps instead of AppProps

Line 5: We get the Layout component by relying on Components.Layout property and Layouts map.

Fixing Error: 'Layout' cannot be used as a JSX component.

This is a common error that happens when we have different return values for our Layout components.

To fix this, we just wrap all your elements in <React.Fragment> even if you're only returning props.children. This ensures that you always return a JSX.Element. For example: 

const NoLayout = (props: PropsWithChildren) => {
  return <>{props.children}</>;
};
// hovering on NoLayout will give you this definition: const NoLayout: (props: PropsWithChildren) => JSX.Element
export default NoLayout;
12345

If we don't do this, we get the following error or similar:

'Layout' cannot be used as a JSX component.
  Its return type 'string | number | boolean | ReactFragment | Element | null | undefined' is not a valid JSX element.
    Type 'undefined' is not assignable to type 'Element | null'
12

Step 5 - Creating Pages with Layouts #

Now that we're done with the setup. We just have to create page components with the Layout property. It is important to note that we should use MyPage type to get the type safety when assigning a Layout

For example, adding a layout to pages/index.tsx would be done like this:

import React from "react";
import { MyPage } from "components/common/types";
const HomePage: MyPage = () => {
  return (
    <div className="container">
      <div className="grid place-content-center min-h-screen">
        <div className="flex flex-col items-center gap-4">
          <h1 className="text-4xl my-8">
            Welcome to NextJS Multiple Layouts Tutorial
          </h1>
        </div>
      </div>
    </div>
  );
};
export default HomePage;
HomePage.Layout = "Main";
// HomePage.Layout = "OtherLayout"; -> error Type '"OtherLayout"' is not assignable to type '"Main" | "Admin" | undefined'.
123456789101112131415161718

Let's add contact and about pages:

Contact Page:

// pages/contact.tsx
import React from "react";
import { MyPage } from "components/common/types";
const ContactPage: MyPage = () => {
  return <div>ContactPage</div>;
};
export default ContactPage;
ContactPage.Layout = "Main";
12345678

About Page:

// pages/about.tsx
import { MyPage } from "components/common/types";
import React from "react";
const AboutPage: MyPage = (props) => {
  return <div>About Page</div>;
};
export default AboutPage;
AboutPage.Layout = "Main";
12345678

Next, let's add the admin pages that use the "Admin" layout.

Dashboard Page:

// pages/admin/index.tsx
import { MyPage } from "components/common/types";
import React from "react";
const AdminDashboardPage: MyPage = () => {
  return <div>AdminDashboardPage</div>;
};
export default AdminDashboardPage;
AdminDashboardPage.Layout = "Admin";
12345678

Users Page:

// pages/admin/users.tsx
import { MyPage } from "components/common/types";
import React from "react";
const UserListPage: MyPage = () => {
  return <div>UserListPage</div>;
};
export default UserListPage;
UserListPage.Layout = "Admin";
12345678

Demo #

That's it! Once you're done with all the steps, you should have different layouts for /admin and regular pages:

Next JS Multiple Layouts Demo

Full Code #

Conclusion #

We learned how to implement multiple layouts in NextJS. By using this technique we ensure that our code is type-safe less error-prone, and easy to understand and maintain when you have several people sharing the code base.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Credits: Image by Jörg Vieli from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle