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:
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:
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:
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
:
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
andgetServerSideProps
in our pages, but not in our layouts.
That's how we can create custom layout in Next.js.