🤝
Next.js12.xのLayouts機能
Next.jsのLayoutについてTypeScriptの記事を探してもあまり見つからなかったので
Layoutsとは🤔
ページ間で共通のコンポーネントを定義する機能
HeaderやFooterなどはほぼ全ページ同じレイアウトを使うけど、ページコンポーネントに直接書くとコンポーネントの状態(入力値など)が保持できないのでページコンポーネントの外に書くことでレイアウトの状態も保持できる🤝
検証に使ったリポジトリ
Next.jsのLayoutsを使うと何が嬉しいのか
一部共通ではないページが有る場合もページ単位でLayoutを変えられるので対応可能😎
下記の共通コンポーネントをNext.jsのLayoutsに置き換えてみる
共通のcomponents
components/Header/Header.tsx
import { memo } from 'react'
let render = 0
export const Header = memo(() => {
console.log(`render${render}`)
render++
return <header>header</header>
})
components/Footer/Footer.tsx
import { memo } from 'react'
export const Footer = memo(() => <footer>footer</footer>)
layout/Layout.tsx
import { ReactElement } from 'react'
import { Footer, Header } from '@src/component/ui'
type LayoutProps = Required<{
readonly children: ReactElement
}>
export const Layout = ({ children }: LayoutProps) => (
<>
<Header />
{children}
<Footer />
</>
)
_app.tsxに直接書くパターン
pages/_app.tsx
import type { AppProps } from 'next/app'
import { Layout } from '@src/layouts'
function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
export default MyApp
メリット
- _app.tsxに書くだけで済む
デメリット
- 共通のレイアウト以外が出てきた時に成立しない
絶対共通のLayoutになるとは限らないしデメリットの方が大きいので、Next.jsのLayoutsを使いページ単位で共通するLayoutを定義した方が良い☺️
Next.jsのLayoutsを使うパターン
nextの型を拡張する
型は公式から引用
types/index.d.ts
import type { NextPage, NextPageWithLayout } from 'next'
import type { AppProps } from 'next/app'
import type { ReactElement } from 'react'
declare module 'next' {
type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactElement
}
}
declare module 'next/app' {
type AppPropsWithLayout<P = {}> = AppProps<P> & {
Component: NextPageWithLayout<P>
}
}
_app.tsxにgetLayoutを追記する
Component.getLayoutがない場合はそのままpageを返す
pages/_app.tsx
-import type { AppProps } from 'next/app'
+import type { AppPropsWithLayout } from 'next/app'
-import { Layout } from '@src/layouts'
-function MyApp({ Component, pageProps }: AppProps) {
+function MyApp({ Component, pageProps }: AppPropsWithLayout) {
+ const getLayout = Component.getLayout ?? ((page) => page)
- return (
- <Layout>
- <Component {...pageProps} />
- </Layout>
- )
+ return getLayout(<Component {...pageProps} />)
}
export default MyApp
共通レイアウトを各ページで呼び出す
pages/index.tsxにgetLayoutを追記する
pages/index.tsx
-import type { NextPage } from 'next'
+import type { NextPageWithLayout } from 'next'
import { HomePage } from '@src/component/pages/Home'
+import { Layout } from '@src/layouts'
-const Home: NextPage = () => <HomePage />
+const Home: NextPageWithLayout = () => <HomePage />
+Home.getLayout = (page) => <Layout>{page}</Layout>
export default Home
pages/about/index.tsxにgetLayoutを追記する
pages/about/index.tsx
-import type { NextPage } from 'next'
+import type { NextPageWithLayout } from 'next'
import { AboutPage } from '@src/component/pages/About'
+import { Layout } from '@src/layouts'
-const About: NextPage = () => <AboutPage />
+const About: NextPageWithLayout = () => <AboutPage />
+About.getLayout = (page) => <Layout>{page}</Layout>
export default About
別のLayoutを適応したい時
pages/work/index.tsx
import type { NextPageWithLayout } from 'next'
import { WorkPage } from '@src/component/pages/Work'
const Work: NextPageWithLayout = () => <WorkPage />
Work.getLayout = (page) => (
<>
<header>work header</header>
{page}
<footer>work footer</footer>
</>
)
export default Work
おまけ
AppPropsWithLayout
を拡張して色々できそうなので、面白いことができたらまた記事を書くかも😏
Discussion