Next.jsのgetLayoutパターンを実際にプロダクトで使用してみてのtips
はじめに
この記事ではNext.jsの公式ドキュメントに記載されているLayoutsパターンであるgetLayoutパターン(Per-Page Layouts)を実際にプロダクトで使用してみてのtipsを書いていきたいと思います。
ちなみに2022年7月時点でNext.jsは改善されたLayouts機能のRFCを公開しており、Layoutsパターンはその機能がリリースされるまでの繋ぎという立ち位置になっていると思います。
2022年7月時点のNext.jsにおけるLayouts
まずNext.jsにおけるLayoutsとは「ページ間で頻繁に再利用されるコンポーネント(ヘッダーやフッターなど)をどこに、どのように書くか」というものです。
このうち最もシンプルなのがpages/_app.tsx
に実装するパターン(Single Shared Layout with Custom App)です。
import { AppProps } from "next/app"
import Layout from '../components/layout'
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
上記の実装ではページごとに任意のLayoutコンポーネントを使用することができません。
例えば、普通のページではLayoutコンポーネントを、マイページではMypageLayoutコンポーネントを使用したいといった場合です。
このような場合に使用されるのがgetLayoutパターンです。
...
export const getLayout = (page: React.ReactElement) => {
return <Layout>{page}</Layout>
}
import { getLayout } from '../components/layout'
export default function TopPage() {
return <h1>Top Page</h1>
}
TopPage.getLayout = getLayout
import { NextPage } from 'next'
import { AppProps } from 'next/app'
import React from 'react'
type NextPageWithLayout = NextPage & {
getLayout?: (page: React.ReactElement) => React.ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
export default function _App({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
上記をベースに実際にプロダクトで使用してみてのtipsを書いていきます。
tips
LayoutにPropsを渡したい
getLayoutは関数化してpages
配下でimportして使用していましたが、LayoutによってはPropsを渡したい場合がありました。
そこでgetLayoutを返す高階関数を定義し、それをpages
配下でimportして使用するようにしました。
...
export const createGetLayout = (
layoutProps?: LayoutProps
): ((page: React.ReactElement) => React.ReactNode) => {
return function getLayout(page: React.ReactElement) {
return <Layout {...layoutProps}>{page}</Layout>
}
}
import { createGetLayout } from '../components/layout'
export default function TopPage() {
return <h1>Top Page</h1>
}
TopPage.getLayout = createGetLayout()
Layoutで状態を持っている場合にページ遷移で状態を初期化したい
getLayoutパターンではLayoutで状態を持っている場合にページ遷移で状態を保持できることはメリットの1つです。
しかし、場合によってはページ遷移で状態を初期化したい場合もあります。
例えば、ヘッダーのハンバーガーメニューの開閉状態について、ハンバーガーメニューを開きリンクをクリックしてページ遷移した場合はハンバーガーメニューを閉じたいといった場合です。
こちらは以下のような実装で対処しました。
...
const router = useRouter()
React.useEffect(() => {
router.events.on('routeChangeStart', onClose) // ページ遷移を開始したらハンバーガーメニューを閉じる関数(onClose)を実行
return () => {
router.events.off('routeChangeStart', onClose)
}
}, [router.events, onClose])
(追記)
Twitterにて初期化したいコンポーネントのkey
にユニークな値(URLなど)を渡すことで強制的に初期化する方法を教えていただきました。
実際にkey
にnext/router
のasPath
を渡してみたところ、上記の実装と同じ挙動を実現できました。
以下のツイートにもあるように、routeChangeStart
時に実行する関数を考慮する必要がなくシンプルなので、こちらの実装の方がよさそうです。
おわりに
以上です。
Layout RFCの機能が実際にリリースされるとお役御免にはなりそうですが、現状はgetLayoutパターンで実装するのがよさそうです。
それではよいNext.jsライフを!
Discussion