In this post, we will learn how to create different layouts for different NextJS pages. We'll examine how this is done at increaser.org, which incorporates four layout variations. Although Increaser's repository is private, you can find all the code in the RadzionKit repository as it also uses different layouts for the landing page and the remaining pages.
We could use different layout components directly in the page component. However, the issue arises when we navigate between pages with the same layout. The layout ends up being re-rendered. This becomes problematic when you have a sidebar layout with a scroll, and, as you scroll down and move to another page, the scroll resets. Thus, we need a method that allows different layouts for different pages, while also maintaining the layout when we navigate between pages with the same layout for improved user experience.
At Increaser, we implement four different layouts:
To accomplish this, we use three helper functions: makeAppPage
, makeAuthPage
, and makeProjectsPage
.
import { Page, GetLayout } from "layout/Page"
import { AppPageLayout } from "focus/components/AppPageLayout"
const getAppPageLayout: GetLayout = (page) => (
<AppPageLayout>{page}</AppPageLayout>
)
export const makeAppPage = (page: Page) => {
page.getLayout = getAppPageLayout
return page
}
All of these functions look alike. Each is a function that takes a component and adds a getLayout
function to it, which wraps the page within a specific layout.
import { NextPage } from "next"
import { ReactNode } from "react"
export type GetLayout = (page: ReactNode) => ReactNode
export type Page<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: GetLayout
}
The Page
type is simply a NextJS page with an extra function. The GetLayout
type describes a function that takes one React node and returns another React node.
Here is how we use our helpers: we wrap the page component with the helper function, as exemplified in this sign-up page.
import { SignUpContent } from "auth/components/SignUpContent"
import { makeAuthPage } from "layout/makeAuthPage"
export default makeAuthPage(SignUpContent)
Without the helper, we would have more code to write:
import { SignUpContent } from "auth/components/SignUpContent"
import { AppPageLayout } from "focus/components/AppPageLayout"
import { Page } from "layout/Page"
const SignUpPage: Page = SignUpContent
export default SignUpPage
SignUpPage.getLayout = (page) => <AppPageLayout>{page}</AppPageLayout>
In which context do we call the getLayout
function?
function MyApp({ Component, pageProps }: MyAppProps) {
const getLayout = Component.getLayout || ((page: ReactNode) => page)
const component = getLayout(<Component {...pageProps} />)
return (
<ThemeProvider>
<GlobalStyle fontFamily={inter.style.fontFamily} />
{component}
</ThemeProvider>
)
}
export default MyApp
It will be the _app.tsx
file that calls the getLayout
function. If the page lacks a getLayout
function like the focus page at Increaser, it will simply return the page component.