Next.js:ページレイアウトを正しく設定する方法
Next.js でページに共通のレイアウトを設定したり、ページごとにレイアウトを切り変えたりする場合 _app.tsx をカスタマイズしてレイアウトを切り替える必要がある
https://nextjs.org/docs/basic-features/layouts
単純にページ側にレイアウトのコンポーネントを配置する形を取ると、コンポーネントの再マウントや再レンダリングが発生する。
特にレイアウトコンポーネントに状態を持つとき(useStateを使用する場合など)再マウントによりレイアウト中に存在する状態がリセットされる。
これを回避するために Context や Recoil、Redux を使用する場合もあるが、多量の再マウントや再レンダリングが発生することで、パフォーマンス的には不利になるのでおすすめできない。
_app.tsx をカスタムして単一のレイアウトを持つ例
すべてのページでレイアウトが同じ場合、_app.tsx でレイアウトを設定する
ページ側にレイアウトを担当するコンポーネントを配置してはいけない
code:_app.ts
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
import Layout from '../components/layout'
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
export default MyApp
_app.tsx をカスタムして複数のレイアウトを持つ例
公式の例に従い _app.tsx を以下のように設置する
ページごとにレイアウトを変更するためページコンポーネント側に用意する関数に対応する設定を行う
code:_app.ts
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
export default MyApp
レイアウトを持つページ側では以下のようにレイアウトを設定する。
やはりページのコンポーネントにはレイアウトを直接設定することはしない
代わりにレイアウトを返す関数を用意し _app.tsx に先程用意した関数で処理できるようにする
code:page_a.ts
import type { ReactElement } from 'react'
import type { NextPageWithLayout } from './_app'
import { PropsWithChildren } from "react"
import { Layout } from '../components/Layout'
const Page: NextPageWithLayout = (props: PropsWithChildren) => {
return <><h3>Page</h3>{props.children}</>
}
Page.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>{page}</Layout>
)
}
export default Page
レイアウトがページによってネストする場合は、Page.getLayout の下で組み上げるようにする。
ページコンポーネントの内部でレイアウトの設定をしてはいけない
あくまでページはページのコンテンツに集中する必要がありページ内のコンテンツは切替時に再マウントされる。
code:page_b.ts
Page.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
Next.js Layouts RFC を使用する (近い未来の話)
どういうものかは以下の公式ブログを参考にすると良い。
https://nextjs.org/blog/layouts-rfc#layouts
pages ディレクトリ などで構成されてきた Next.js 固有の仕組みが app ディレクトリで再構築される
※ 2022-10-01 時点ではまだRFCの状態で実装はされていない。
機能概要については以下で議論されている
https://github.com/vercel/next.js/discussions/37136
将来使えるようになるとぐっと楽になりそう。