Creating Custom Layouts in Next.js

Written on December 26, 2022 by Risal Amin

Last updated on October 14, 2023.

View history
3 min read

--- views


React's modular nature allows us to create reusable components, and layouts are no exception. In Next.js, there's a variety of ways to implement custom layouts. In this blog, we'll explore two of the most common approaches:

Single Shared Layout

A Single Shared Layout in Next.js is a custom layout that serves as a blueprint for every page within our application. Consider a scenario where our application has a common structure, featuring a navbar and a footer that remain consistent across all pages. In such cases, we can define our layout like this:

components/layout/layout.tsx
import Navbar from './navbar';
import Footer from './footer';
import type { ReactNode } from 'react';
 
type LayoutProps = {
  children: ReactNode;
};
 
export function Layout({ children }: LayoutProps): JSX.Element {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  );
}

To implement this custom layout, we can wrap the Component within our _app.tsx file:

pages/_app.tsx
import { Layout } from '@components/layout';
import type { AppProps } from 'next/app';
 
export default function App({ Component, pageProps }: AppProps): JSX.Element {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

One cool aspect of the Single Shared Layout is that it is consistently reused across all pages, which can also preserve the state of the layout.

Per-Page Layout

If we want to have different layouts (ex. authentication, dashboard, etc...), we can define a getLayout property to pages that will receive the page in props, and wrap it in the layout that we want. Since getLayout is a function, we can have nested layouts if we want.

Here's an example of a page:

pages/index.tsx
import { Layout } from '@components/layout/layout'
import { NestedLayout } from '@components/layout/nested-layout'
import type { ReactElement, ReactNode } from 'react'
 
function Page(): JSX.Element {
  return (
    // Content specific to our page...
  )
}
 
Page.getLayout = (page: ReactElement): ReactNode => {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}

For this approach to work, we'll need to make a few adjustments to our _app.tsx:

pages/_app.tsx
import type { ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import type { AppProps } from 'next/app';
 
type NextPageWithLayout = NextPage & {
  // Define a getLayout function for each page
  getLayout?: (page: ReactElement) => ReactNode;
};
 
type AppPropsWithLayout = AppProps & {
  // Override the default Component type that comes with AppProps
  Component: NextPageWithLayout;
};
 
export default function App({
  Component,
  pageProps
}: AppPropsWithLayout): JSX.Element {
  /**
   * Use the getLayout function defined on the page if available
   * Otherwise, fallback to using the default layout that wraps the page contents
   */
  const getLayout = Component.getLayout ?? ((page): ReactNode => page);
 
  return getLayout(<Component {...pageProps} />);
}

It's worth noting that Custom Layouts aren't considered as pages. So, if we want to fetch data inside our layout, we'll need to do it on the client-side. we can still use getStaticProps and getServerSideProps in our pages, but not in our layouts.

That's how we can create custom layout in Next.js.

Other posts you might like

← Back to blogEdit this on GitHub