Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】useSyncExternalStoreを使ったSSRの判定

2025年09月25日に「Zenn」が公開したITニュース「useSyncExternalStoreを使ったSSRの判定」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

ReactのuseSyncExternalStoreは、SSR中かハイドレーション中かを判定する。React AriaのuseIsSSRフックがこの技術を活用し、Webアプリの表示状態を把握する。

出典: useSyncExternalStoreを使ったSSRの判定 | Zenn公開日:

ITニュース解説

Webアプリケーション開発の世界では、ユーザーに素早くコンテンツを届け、快適な操作体験を提供するために、様々な技術が活用されている。その中でも「SSR(サーバーサイドレンダリング)」は非常に重要な役割を担っている技術の一つだ。ReactのようなJavaScriptライブラリを使ってWebサイトを構築する際、ブラウザだけでなくサーバー側でもコンポーネントを事前にHTMLとして生成しておくことで、初期表示速度の向上やSEO対策に効果を発揮する。しかし、サーバーとブラウザ(クライアント)では実行環境が異なるため、それぞれで特定の処理を切り替えたい場面がしばしば発生する。例えば、ブラウザでのみ利用できるAPI(windowオブジェクトなど)に依存する処理は、サーバーでは実行できない。このような時に、現在がサーバーサイドレンダリング中なのか、それともブラウザでのクライアントサイドレンダリング中(あるいはハイドレーション中)なのかを正確に判定する技術が必要となる。

ここで登場するのが、Reactが提供するフックの一種であるuseIsSSRだ。これは、現在がサーバーサイドレンダリング中か、またはハイドレーション中であるかを判定するためのフックで、例えばReact AriaというUIライブラリで使用されている。この記事では、このuseIsSSRがどのようにしてSSR判定を行っているのか、その核心に迫る。

まず、一般的なSSR判定方法とその課題を考えてみよう。最も単純な方法は、ReactのuseStateフックとuseEffectフックを組み合わせるものだ。 useStateで初期値をtrue(SSR中と仮定)に設定し、useEffectの中でsetClient(false)のように状態を更新する。useEffectはブラウザでの初回レンダリング後に実行されるため、サーバーでは初期値のtrueが使われ、ブラウザでレンダリングが完了した後にfalseに切り替わる。しかし、この方法には問題がある。サーバーで生成されたHTMLにはtrueの状態が埋め込まれるが、ブラウザでそのHTMLとReactの仮想DOMが結びつけられる「ハイドレーション」の段階で、useEffectが実行されるまでのごく短い間、状態がtrueのままとなり、その後falseに切り替わる。この一瞬の不一致が、視覚的なちらつきやハイドレーションエラー(サーバーとクライアントでDOMの構造が異なるために発生するエラー)を引き起こす可能性がある。

次に、useEffectよりも早く実行されるuseLayoutEffectフックを利用する方法を考える。useLayoutEffectはブラウザのDOM更新後、ペイント前というタイミングで同期的に実行されるフックだ。useStateで初期値をtrueに設定し、useLayoutEffectの中でsetClient(false)とする。この方法だとuseEffectよりは早く状態が切り替わるように思えるかもしれない。しかし、useLayoutEffectuseEffectと同様に、サーバーサイドレンダリング時には実行されないという特性がある。そのため、サーバーではやはり初期値のtrueが使われ、ブラウザでハイドレーションが完了し、初めてuseLayoutEffectが実行されるタイミングでfalseに切り替わる。結果的に、やはりハイドレーション時の不一致という課題は解決できない。

そこで、これらの課題を解決するために利用されるのが、React 18から導入されたuseSyncExternalStoreフックだ。このフックは、Reactの外部にある状態管理ライブラリ(例えばReduxのようなもの)の状態を、Reactのコンポーネントと同期させるために設計されたものだ。useSyncExternalStoreには、主に二つの引数を渡す必要がある。一つはsubscribe関数で、外部ストアの状態が変化したときにReactに再レンダリングを促すためのリスナーを登録する。もう一つはgetSnapshot関数で、外部ストアから現在の状態のスナップショット(特定の時点での状態)を取得する。

このuseSyncExternalStoreがSSR判定にどのように役立つのかが重要だ。その鍵は、getSnapshot関数の実行タイミングにある。getSnapshotは、サーバーサイドレンダリング中と、クライアントサイドでの初回レンダリング時(ハイドレーション時を含む)に、同期的に実行されるという特別な特性を持っている。この特性が、先述したuseStateuseLayoutEffectの課題を解決する突破口となる。

useSyncExternalStoreを使ったSSR判定の具体的な実装では、getSnapshot関数の中で、ブラウザでのみ利用可能なwindowオブジェクトが存在するかどうかをチェックする。

  • サーバーサイドレンダリング時: サーバー環境にはwindowオブジェクトが存在しないため、getSnapshot関数はtrueを返すようにする。これは「現在がSSR中である」ということを意味する。
  • クライアントサイドでの初回レンダリング時(ハイドレーション時): ブラウザ環境ではwindowオブジェクトが常に存在する。そのため、getSnapshot関数はfalseを返すようにする。これは「現在がクライアントサイドである」ということを意味する。

このように実装することで、サーバーでレンダリングされたHTMLには、すでに「windowオブジェクトがない(つまりSSR中である)」という判定結果(true)が埋め込まれる。そして、ブラウザでのハイドレーションが始まる際、getSnapshotが同期的に実行され、「windowオブジェクトがある(つまりクライアントサイドである)」という判定結果(false)がすぐに取得される。

useSyncExternalStoreのもう一つの引数であるsubscribe関数は、SSR判定の目的においては特に状態変化を監視する必要がないため、何もしない空の関数を渡すだけで問題ない。

このuseSyncExternalStoreを活用した方法の最大の利点は、サーバーで生成されたHTMLに埋め込まれる状態と、ブラウザでハイドレーション中にReactが認識する状態が、最初から一致している点だ。サーバーではtrue、クライアントではfalseという値が、それぞれの環境で期待通りに、かつ同期的に得られるため、ハイドレーション時の不整合や視覚的なちらつきを完全に回避できる。

まとめると、useSyncExternalStoreは、外部ストアとの同期という本来の目的を超えて、そのgetSnapshot関数の特別な実行タイミングを利用することで、サーバーとクライアントの実行環境を正確かつ同期的に判定する強力な手段となる。これにより、開発者はハイドレーションミスを気にすることなく、より堅牢でパフォーマンスの高いReactアプリケーションを構築できるようになる。これは、Reactの内部的な動作やライフサイクルを深く理解し、その特性を巧みに活用した高度なテクニックの一つと言える。

関連コンテンツ