Next.jsのHTMLレンダリングを理解する
はじめにざっくりレンダリングの歴史のおさらい
古典的なWebサイトやWebサービスはWebサーバー上でHTMLをレンダリングしブラウザへと送信していましたが、Ajaxという技術の発明やスマートフォンの登場、後のネイティブアプリの登場によってユーザーの閲覧環境がマルチクライアント化した結果、WebサーバーはAPIを介してクライアントに情報を送ることのみに注力し、各クライアントがUIをレンダリングする手法に移り変わっていきました。
しかし、ことブラウザでのレンダリングにおいては検索botへの対応やクライアント側のコンピューティング能力の非力さ故のパフォーマンス低下など、様々な問題がありその揺り戻しが起こります。
クライアントでのレンダリングに特化したReactのようなフレームワークが登場して以降、書き味はそのままにサーバー側でReactで書かれたコードをレンダリングする試みが行われ、結果現在はハイブリッドにサーバーとクライアント双方の良さを用いてUIをレンダリングする方法が取られることも選択肢になっています。
Next.jsでもその方法が用いられていますが、中々全容を理解するのが大変なので、順を追って説明していきます。と言ってもこの記事は基本的には以下の内容を要約したものです。
サーバーサイドレンダリング(SSR)
メリット
ある種古典的なレンダリング方法で、サーバー単体でのコンピューティングによってHTMLをレンダリングできます。そのため同一のデータを一斉に配信する用途などに適しており、クライアントに処理をさせる必要のあるJavascriptの配信量も少なくなり、データ通信の節約にもつながります。また、DBやAPIなどのサーバーとの通信ロジックはクライアントに開示する必要が無いためセキュアな通信が実装しやすい一面もあります。
レンダリングの手順
Next.jsでもデフォルトではSSRが行われます。その順序は以下のとおりです。
- Reactのコードを表示するデータ内容に従って
Reactサーバーコンポーネントペイロード
(RSCペイロード)という形式に変換します。 - その後RSCペイロードと後述する
クライアントコンポーネント
のJavascriptコードを利用し、サーバー上でHTMLをレンダリングします。 - その後クライアントはHTMLを受け取り、まず非インタラクティブな画面をスピーディーに描画します。
レンダリングのタイミング
以下のとおり行われます。
- ビルド時
- ブログ投稿や製品ページなど、ユーザー向けにパーソナライズされていないデータソースへのアクセスはこの際に行われ、HTMLが事前にレンダリングされ、キャッシュされます(full route cacheと呼ばれる)。これはSSGと呼ばれるものと同義と考えて良いと思います。
- データの再検証時
- 具体的な実装の説明は省きますが、Next.jsでは各ページ毎に自身の賞味期限を設定することができます。期限をすぎたページはたとえビルド時にレンダリングが行われていようともアクセスを契機に再度レンダリングが行われます。この際のレンダリング結果はキャッシュされ、次の期限まで有効となります。こちらはISRと呼ばれるものですね。
- 動的なデータへのアクセス
- 動的なデータとはリクエスト時にのみ知ることができる情報のことで、ユーザー向けにパーソナライズされたデータや、ユーザーが入力した検索パラメータなどが該当します。これらの情報が必要なページはビルド時にレンダリングすることができないため、オンデマンドでアクセス時にレンダリングされます。
ストリーミング
聞き慣れた用語ではありますが、Next.jsの文脈では比較的新しい技術であるストリーミングSSRを指しています。
この技術を利用するとサーバーからレスポンスを受けている最中にブラウザーはUIを段階的にレンダリングします。細かい単位でリクエストとレスポンスを繰り返すわけではなく、1つのリクエスト中に段階的なUIのレンダリングをしているのです。
どうやって?それはHTTPヘッダーに書かれるTransfer-Encoding: chunked
とモダンブラウザーの構文補正の振る舞いにより実現されています。
HTTPヘッダーにTransfer-Encoding: chunked
が宣言されて配信されるコンテンツはデータチャンク(塊)として分割され連続的に送られることが期待されます。そしてモダンなブラウザーはHTML構文に間違い(例えばタグの閉じ忘れ)などがあってもその間違いを補正し、正常にレンダリングすることができます。
Next.jsはこの特性を利用し、データチャンクを Suspense
タグで囲われたコンポーネントの単位に分割し、順番に送信します。
このchunkはそれぞれdom中のどの位置にインサートされるべきかというレイアウト情報も持っているため、単純に上から順番に配置されることはなく、適切なレイアウトに復元されます。
クライアントサイドレンダリング(CSR)
メリット
クライアントサイドレンダリングの利点は、ユーザーアクションを契機にしたインタラクティブなUIの変更を即時に行えること、またはブラウザAPIにアクセスし地理情報などを利用した体験を提供できることにあります。
利用方法
Next.jsにおいて上記のようなメリットを享受したい場合、コンポーネントに"use client"
ディレクティブを追加してそれ以下のコンポーネントをクライアントコンポーネント(CC)であると宣言する必要があります。
CCとして宣言されたコンポーネントはクライアントで描画されたのちにハイドレートされ、ユーザーアクションのためのイベントリスナーや、ブラウザAPIなどの利用が開始されます。
レンダリングのタイミング
SSRの段階で述べた通り、CCとはいえ、まずはサーバー上で事前にレンダリングが行われます。
では実際にCSRが行われるのはどういったタイミングでしょうか?それは最初のページ読み込み以降、つまり画面遷移などが行われて新たにリクエストが発生するときです。
Next.jsのLink機能はこの際に画面全体の再読み込みは行わず、必要なコンポーネントのみをリクエストします。
レスポンスはRSCペイロードとCCであり、それらはクライアントサイドでレンダリングされます。
まとめ
- Next.jsは基本的にSSRを行う
- SSRのタイミングはビルド時・再検証時・それ以外はオンデマンドである
- ストリーミングSSRはレスポンスを分割して送信し、随時表示させる
- クライアントコンポーネントもSSRされる
- 画面遷移時にはCSRが行われる
おわり
ここまででNext.jsがどのようにHTMLをレンダリングするかの大枠が把握できたと思います。
2024年、私は久しぶりにWeb開発に戻ってきたので意外とキャッチアップが多く、特にストリーミングSSRの仕様について調査と理解をするのは、結構な時間を要しました。
Next.jsは公式ドキュメントがそこそこ充実しているので、はじめに掲載したレンダリングに関するものと、キャッシュに関するドキュメントは頭に入れておくことをおすすめします。
2025年もパフォーマンスとより良いユーザー体験を意識した開発を心がけていきたいですね。
Discussion