Next.js + redux-persist の落とし穴に注意しよう

# TL; DR

Next.js で `redux-persist` を使って store を LocalStorage に保存したい時、`_app.js` の `<Component {...pageProps} />` 全体を `PersistGate` で囲うのではなく、個々の Component を `PersistGate` で囲うようにしよう

※ 以下の記事に既に同様のことが書かれています。参考にさせていただきました。
 https://qiita.com/miyabiya/items/14e4f133d5df5d53cd77

# 落とし穴

Next.js で構築していた[自分のサイト](https://hellorusk.net)が Twitter Cards の生成に失敗しているのに気づいた。よく確認すると SSR で meta タグを吐き出すのに失敗している。
そのときの構成はこんな感じ。

```tsx:pages/_app.tsx
import App, { Container } from "next/app";
import Layout from "../layouts/page-layout";
import { Provider } from "react-redux";
import store, { persistor } from "../store/store";
import { PersistGate } from "redux-persist/integration/react";

export default class MyApp extends App {
  render () {
    const { Component, pageProps, router } = this.props;

    return (
      <Container>
        <Provider store={store}>
          <PersistGate loading={null} persistor={persistor}>
            <Component {...pageProps} key={router.route} />
            <Layout />
          </PersistGate>
        </Provider>
      </Container>
    );
  }
}
```

```tsx:pages/_documents.tsx
import Document, { Head, Main, NextScript } from "next/document";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps };
  }

  render() {
    return (
      <html lang="ja">
        <Head>
          <link rel="shortcut icon" href="/static/favicon.ico" />
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
          <meta name="theme-color" content="#000000" />
          <meta name="description" content="HelloRusk Official Website." />
          <meta property="og:image" content="https://hellorusk.net/static/mika_square.png" />
          <meta name="twitter:card" content="summary" />
          <meta name="twitter:site" content="@HelloRusk" />
          <meta name="twitter:image" content="https://hellorusk.net/static/mika_square.png" />
          <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+JP:300&display=swap" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}
```

```pages/それぞれのページ.tsx
import Head from "next/head";
import { hogemode, fugamode } from "../store/actions";
import { ModeState, ActionTypes } from "../store/types";
import HogeComponent from "../components/hoge";
import { connect } from "react-redux";

interface HogeProps {
  isHogekMode: boolean,
  hogemode: () => ActionTypes,
  fugamode: () => ActionTypes
}

const Index: React.FC<HogeProps> = props => {
  const handleChange = () => {
    props.isHogeMode ? props.hogemode() : props.fugamode();
  };

  return (
    <div>
      <Head>
        <title key="title">title</title>
        <meta property="og:title" content="このページのtitle" /> 
        <meta property="og:url" content="このページのurl" /> 
        <meta property="og:description" content="このページの概要" />
      </Head>
      // 何らかの store を持つ Component
      <HogeComponent
        checked={props.isHogeMode}
        onChange={() => handleChange()}
      />
    </div>
  );
};

const mapStateToProps = (state: ModeState) => {
  return {
    isHogeMode: state.isHogeMode,
  };
};

const mapDispatchToProps = {
  hogemode,
  fugamode
};

export default connect(mapStateToProps, mapDispatchToProps)(Index);
```

`_app.js` を見ればわかるように、全体を `PersistGate` で囲っている。
これによって、`PersistGate` 配下の Component は、LocalStorage にアクセスして状態を取り出すのを待ってからレンダリングが行われるようになる。画面表示的には何も問題はない。

問題は、これによって `それぞれのページ.tsx` にある `<Head>` 内の meta タグ

```html
      <Head>
        <title key="title">title</title>
        <meta property="og:title" content="このページのtitle" /> 
        <meta property="og:url" content="このページのurl" /> 
        <meta property="og:description" content="このページの概要" />
      </Head>
```

この部分が SSR されなくなっていたことにある。よって、GET で得られる html にも meta タグが正しく記述されておらず、Twitter Cards などのOGP設定に失敗していた。

# 対策

`PersistGate` の粒度を上げればよい。
すなわち、大雑把に `_app.js` 全体を囲うのではなく、store を使っている Component それぞれに `PersistGate` を当てればよい。
上の例では、`HogeComponent` を `PersistGate` で囲うことになる。