2 Ways to Implement Page Layouts in NextJs

Last updated : Jul 30, 2023 12:00 AM

Header, Sidebar, Content, and Footer are the most common components of a standard website. Therefore, it makes sense to separate those components into stand-alone files. Then we combine can them with a template, allowing us to eliminate duplicate code throughout the application.

This tutorial explains two different ways to use a template layout in NextJs. First, let's create our template components and a template layout to bind them together. You can Download this project here

To fetch data to our app, we will be using a simple api that returns a JSON.

import type { NextApiRequest, NextApiResponse } from 'next'

export type Data = {
  topMenu: topMenu[],
  post: post[],
  footer: footer
}
export type topMenu = {
  id: string,
  name: string
}
export type post = {
  title: string,
  text: string
}
export type footer = {
  text: string,
  year: string
}
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
  res.status(200).json({
    "topMenu": [{ "id": "1", "name": "Menu 1" }, { "id": "2", "name": "Menu 2" }, { "id": "3", "name": "Menu 3" }],
    "post": [{ "title": "React JS", "text": "How to use Redux" }, { "title": "Next Js", "text": "How to use Context api" }, { "title": "JavaScript", "text": "What is a closure?" }, { "title": "TypeScript", "text": "What is type checking?" }, { "title": "JQuery", "text": "JQuery interview questions" }],
    "footer": { "text": "LearnBestCoding", "year": "2022" }
  })
}

The sample app we develop will have three pages and a layout template. The layout is listed below:

import { ReactNode } from 'react';
import { Data } from '../pages/api/api';
import Footer from "./footer";
import Header from "./header";

type Props = {
    data: Data,
    children: ReactNode
}
const Layout = ({ data, children }: Props) => {
    const { topMenu, footer } = data
    return (
        <>
            <Header menu={topMenu} />
            {children}
            <Footer {...footer} />
        </>
    )
}
export default Layout;

Note that we pass data from each page to the layout. You cannot use getServerSideProps in non-page components. Therefore, if you want to serverside-render the layout template, you need to pass the data that the layout uses.

Method 1: Wrapping every page in the Layout template

Wrapping each page in the layout is the least complicated way to use a layout template across the application. It is demonstrated below.

//template1.tsx
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import Layout from '../../layout/layout'
import { post } from './api/api';

export default function Template1({ data }: InferGetServerSidePropsType<typeof getServerSideProps>) {
    return (
        <Layout data={data}>
            <>
                {data.post.map((post: post) => {
                    return <p>{post.title}</p>
                })}
            </>
        </Layout>
    )
}

export const getServerSideProps: GetServerSideProps = async () => {
    const res = await fetch(`http://localhost:3000/api/api`)
    const data = await res.json()
    return { props: { data: data } }
}
//_app.tsx
import { NextPage } from 'next'
import type { AppProps } from 'next/app'
import { ReactElement, ReactNode } from 'react'

type NextPageWithLayout = NextPage & {
  getLayout?: (pageProps: AppProps, page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
    return <Component {...pageProps} />;
}
export default MyApp

Method 2: Using a layout function in each page component

The second method is to have a function that returns a layout. This function resides in each page component. The layout template is retrieved in _app.js and resoled during render time.

//template2.tsx
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { ReactElement } from 'react';
import Layout from '../layout/layout'
import { Data, post } from './api/api';

export default function Template2({ data }: InferGetServerSidePropsType<typeof getServerSideProps>) {
    return (
        <>
            {data.post.map((post: post) => {
                return <p>{post.title}</p>
            })}
        </>
    )
}
export const getServerSideProps: GetServerSideProps = async () => {
    const res = await fetch(`http://localhost:3000/api/api`)
    const data = await res.json()
    return { props: { data: data } }
}
Template2.getLayout = function getLayout(data: Data, component: ReactElement) {
    return (
        <Layout data={data}>
            {component}
        </Layout>
    )
}
//_app.tsx
import { NextPage } from 'next'
import type { AppProps } from 'next/app'
import { ReactElement, ReactNode } from 'react'

type NextPageWithLayout = NextPage & {
  getLayout?: (pageProps: AppProps, page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  if (Component.getLayout) {
    return Component.getLayout(pageProps.data, <Component {...pageProps} />);
  }
  else {
    return <Component {...pageProps} />;
  }
}
export default MyApp

One advantage of the second method is the components can dynamically select a layout template based on function arguments.

Download Nextjs template layout

Lance

By: Lance

Hi, I'm Lance Raney, a dedicated Fullstack Developer based in Oklahoma with over 15 years of exp

Read more...