⚠️

react-helmetとSuspenseを同時に使うときの注意

2024/02/24に公開

react-helmet、SPAで動的にメタ情報を切り替える場合に重宝するパッケージです。自分も長い間各所でお世話になってきました。(現在進行系)

今回はちょっとハマった現象があったので備忘録です。

現象

以下のような前提がある場合、

  • react-helmetでページごとにタイトルを切り替えている
    • 設定していないページもある (上位のページのタイトルが適用される)
  • ページの表示のためのデータを非同期で取得している
  • 非同期データを待つためにSuspenseを利用している

Suspenseを使っているページからタイトルを設定していないページに移動したときに、もとのタイトルに戻りません。

サンプル

簡単なサンプルを作ってみたので動作を見てみたい方はこちらから。
https://github.com/MtBlue81/react-helmet-sample

  • 最初のページのタイトルが Helmet Sample
  • Page1に移動するとタイトルが Page 1
  • 最初のページに戻ると Helmet Sample
  • Page2に移動するとタイトルが Page 2
  • 最初のページに戻っても Page 2

解説

react-helmetは、コンポーネントのライフサイクルを使って呼び出された場所でのtitle,metaなどの情報を書き換えています。
少しシンプルにすると以下のようになっています。

  • mount前に情報書き換え ①
  • unmount前に情報をもとに戻す

この処理をreact-side-effectというパッケージを利用して実現しているのですが、このパッケージが①で利用しているのが UNSAFE_componentWillMount

名前から推測されるとおり非推奨な機能で、利用しているとStrictModeにおいて警告が出るようになっており、これを回避するためにreact-helmet-asyncというforkされたパッケージも出ています。

参考:
https://dev.classmethod.jp/articles/react-helmet-async-instead-of-react-helmet/

ただ、今回の事象の原因はSuspenseでrenderまでは呼ばれるがmountはされない(= unmountが呼ばれない)という点なので、react-helmet-asyncでも対応しきれていません。
react-helmet-asyncは UNSAFE_componentWillMountの代わりにrenderを使っていてunmountでのもとに戻す処理が発動しません。公式でもこういうケースで代わりに利用するのは componentDidMount であるとしています。
ちなみに天下のNext.jsはどうしてるのかな、と見に行ってみるときっちりuse(Layout)Effectを利用していました。
https://github.com/vercel/next.js/blob/aa914eea73aee1bbcf51cf267e0d5464a81a58e3/packages/next/src/shared/lib/side-effect.tsx#L40-L74

結論

Next.jsを使おう(違

Discussion