Ultimate Guide: How to Setup Multiple Layouts in NextJS (Typescript)
Jasser Mark Arioste
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 #
- 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.
- 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.
- It should be possible for a page to have no layout.
- 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:
Full Code #
The full code is available on this URL: https://github.com/jmarioste/next-multiple-layouts-tutorial/tree/tutorial
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