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.
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
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