Custom layout
Published at November 25, 2019
4 min read • Edit this post

Layouts

Examples

The React model allows us to deconstruct a page into a series of components. Many of these components are often reused between pages. For example, you might have the same navigation bar and footer on every page.

JSX
1// components/layout.js
2
3import Navbar from './navbar'
4import Footer from './footer'
5
6export default function Layout({ children }) {
7 return (
8 <>
9 <Navbar />
10 <main>{children}</main>
11 <Footer />
12 </>
13 )
14}

Examples

Single Shared Layout with Custom App

If you only have one layout for your entire application, you can create a Custom App and wrap your application with the layout. Since the <Layout /> component is re-used when changing pages, its component state will be preserved (e.g. input values).

JSX
1// pages/_app.js
2
3import Layout from '../components/layout'
4
5export default function MyApp({ Component, pageProps }) {
6 return (
7 <Layout>
8 <Component {...pageProps} />
9 </Layout>
10 )
11}

Per-Page Layouts

If you need multiple layouts, you can add a property getLayout to your page, allowing you to return a React component for the layout. This allows you to define the layout on a per-page basis. Since we're returning a function, we can have complex nested layouts if desired.

JSX
1// pages/index.js
2
3import Layout from '../components/layout'
4import NestedLayout from '../components/nested-layout'
5
6export default function Page() {
7 return {
8 /** Your content */
9 }
10}
11
12Page.getLayout = function getLayout(page) {
13 return (
14 <Layout>
15 <NestedLayout>{page}</NestedLayout>
16 </Layout>
17 )
18}
JSX
1// pages/_app.js
2
3export default function MyApp({ Component, pageProps }) {
4 // Use the layout defined at the page level, if available
5 const getLayout = Component.getLayout || ((page) => page)
6
7 return getLayout(<Component {...pageProps} />)
8}

When navigating between pages, we want to persist page state (input values, scroll position, etc) for a Single-Page Application (SPA) experience.

This layout pattern enables state persistence because the React component tree is maintained between page transitions. With the component tree, React can understand which elements have changed to preserve state.

Note: This process is called reconciliation, which is how React understands which elements have changed.

With TypeScript

When using TypeScript, you must first create a new type for your pages which includes a getLayout function. Then, you must create a new type for your AppProps which overrides the Component property to use the previously created type.

TSX
1// pages/index.tsx
2
3import type { ReactElement } from 'react'
4import Layout from '../components/layout'
5import NestedLayout from '../components/nested-layout'
6
7export default function Page() {
8 return {
9 /** Your content */
10 }
11}
12
13Page.getLayout = function getLayout(page: ReactElement) {
14 return (
15 <Layout>
16 <NestedLayout>{page}</NestedLayout>
17 </Layout>
18 )
19}
TSX
1// pages/_app.tsx
2
3import type { ReactElement, ReactNode } from 'react'
4import type { NextPage } from 'next'
5import type { AppProps } from 'next/app'
6
7type NextPageWithLayout = NextPage & {
8 getLayout?: (page: ReactElement) => ReactNode
9}
10
11type AppPropsWithLayout = AppProps & {
12 Component: NextPageWithLayout
13}
14
15export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
16 // Use the layout defined at the page level, if available
17 const getLayout = Component.getLayout ?? ((page) => page)
18
19 return getLayout(<Component {...pageProps} />)
20}

Data Fetching

Inside your layout, you can fetch data on the client-side using useEffect or a library like SWR. Because this file is not a Page, you cannot use getStaticProps or getServerSideProps currently.

JSX
1// components/layout.js
2
3import useSWR from 'swr'
4import Navbar from './navbar'
5import Footer from './footer'
6
7export default function Layout({ children }) {
8 const { data, error } = useSWR('/api/navigation', fetcher)
9
10 if (error) return <div>Failed to load</div>
11 if (!data) return <div>Loading...</div>
12
13 return (
14 <>
15 <Navbar links={data.links} />
16 <main>{children}</main>
17 <Footer />
18 </>
19 )
20}

For more information on what to do next, we recommend the following sections:

© 2022 Nam Hoang Le. All Rights Reserved. Made with 🔥 passion, a ⌨️ keyboard and Next.js
githublinkedinmailto