TL; DR
Next.js で redux-persist
を使って store を LocalStorage に保存したい時、_app.js
の <Component {...pageProps} />
全体を PersistGate
で囲うのではなく、個々の Component を PersistGate
で囲うようにしよう
※ 以下の記事に既に同様のことが書かれています。参考にさせていただきました。
https://qiita.com/miyabiya/items/14e4f133d5df5d53cd77
落とし穴
Next.js で構築していた自分のサイトが Twitter Cards の生成に失敗しているのに気づいた。よく確認すると SSR で meta タグを吐き出すのに失敗している。
そのときの構成はこんな感じ。
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>
);
}
}
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>
);
}
}
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 タグ
<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
で囲うことになる。