next tips

Next.js でのレイアウトの作り方と切り替え方

Keita Imatomi
2020-6-11 20:37

概要

ヘッダやナビゲーションなどの、どのページでも使うよねっていう共通のレイアウトコンポーネントを Next.js で作ったときのメモです。

ちなみにReact.js だと

App.tsxの中で react-router-dom を使用して、共通レイアウトとルーティングを作っていく。

App.tsx

import React from 'react' import { BrowserRouter as Router, Switch, Route } from 'react-router-dom' import Header from 'components/Header' import Nav from 'components/Footer' import Footer from 'components/Footer' import Home from 'pages/Home' import PageA from 'pages/PageA' import PageB from 'pages/TermsB' import NotFound from 'pages/NotFound' export default function App() { return ( <Router> <LayoutHeader /> <main role="main" id="contents"> <Switch> <Route exact path="/"> <Home /> </Route> <Route exact path="/page-a"> <PageA /> </Route> <Route path="/page-b"> <PageB /> </Route> <Route path="*"> <NotFound /> </Route> </Switch> </main> <LayoutFooter /> </Router> ) }

本題、Next.js だと

まずは公式をみてみる

Next.jsでは pages ディレクトリ以下のコンポーネントからルーティングが生成される。レイアウトに関しては、公式のチュートリアルを見ると、

こんな感じのLayoutコンポーネントを作って

Layout.tsx

function Layout({ children }) { return <div>{children}</div> } export default Layout

各ページコンポーネント内で、こんな感じで使うよ的なことが書いてある。<Layout></Layout> で挟んだ部分が上記 childrenの部分に入るから、ページがレイアウトでラッピングされる。

pages/index.tsx

import Link from 'next/link' import Layout from '../../components/layout' export default function FirstPost() { return ( <Layout> <h1>First Post</h1> <h2> <Link href="/"> <a>Back to home</a> </Link> </h2> </Layout> ) }

。。。あれ?でも、

(これだとページ遷移する度にレイアウトが持つ状態も破棄して再レンダリングしちゃうのでは?)

と思って、試しにヘッダにつけたプルダウンメニューを開いた状態でページ遷移してみた。そして、状態は綺麗に破棄された。

ナビゲーションメニューにアニメーションなどを入れている場合、アニメーションが遷移をまたげないのは困りますし、なんとなくヘッダなどの共通コンポーネントは状態を保持しつつ、必要な部分だけ更新して欲しい!

と思ってもう少ししべてみたら、ちゃんと Custom App があった。React.js でいうところの上記 App.tsx の部分、Next.js がページをレンダリングする前にやる処理を、 pages/_app.tsx でカスタムできる。

カスタム App

以下のように pages/_app.tsx にLayoutコンポーネントを配置してあげれば、ページ遷移でも状態を維持できた。

pages._app.tsx

import React from 'react' import LayoutMain from '../layouts/LayoutMain' function App({ Component, pageProps }) { return ( <LayoutMain> <Component {...pageProps} /> </LayoutMain> ) } export default App

ページ毎にレイアウトを切り替えたい

例えば、ログイン画面とログイン後のページではレイアウトが異なる、とか管理画面は違うレイアウトにしたい、といったことはよくある。そういったレイアウトの切り替えを行いたい。

しかし、上記のままだと全てのページに同じレイアウトが使用されてしまうので、切り替える処理を追加したい。

ルートのパス名をみて、、、というのは、かっこ悪いのでやりたくない。できれば、Nuxt.js の用にページコンポーネント内にレイアウトを指定するような情報を持たせたい。

pages/_app.tsx の中をよくみると、

function App({ Component, pageProps }) {
  return (
    <LayoutMain>
      <Component {...pageProps} />
    </LayoutMain>
  )
}

気になるものがあった。

pageProps

名前からして、ページが持つプロパティ。これにレイアウト情報を持たせれば良い! つまりこんな感じで切り替えれるようにしたい。

function App({ Component, pageProps }) {
  switch (pageProps.layout) {
    case 'main': {
      return (
        <LayoutMain>
          <Component {...pageProps} />
        </LayoutMain>
      )
    }
    default: {
      return (
        <Component {...pageProps} />
      )
    }
  }
}

ページコンポーネントにレイアウト指定情報を持たせる

各ページコンポーネントにgetServersidePropsを追記し、レイアウト情報を追加する。 getServersidePropsはページがレンダリングされる前にサーバーサイドで実行され、ページに必要なデータをprops ( = pageProps )として渡してくれる。(ちなみに、 getInitialPropsでもいいが、今後は getServerSideProps推しらしい。) あと、サーバーサイドレンダリングではなく、プリレンダリングする場合は getStaticProps を使用する。

export const getServerSideProps = async (context) => ({
  props: {
    layout: true // 複数のレイアウトを切り替えたいときは 'MainLayout' などの文字列を用いる
  }
})

これで一応、Next.js で共通レイアウトを作って、ページに応じて切り替えることができるようになった。

こちらもどうぞ