🐷

Next.js 15で103 EarlyHintsを使う

2024/06/30に公開

Early Hintsについて

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/103

EarlyHintsを利用するとブラウザがHTMLを受け取る前に、必要なリソースのダウンロードを行うことができます。
例えばdevtoolでHTML読み込みを観察したときに「サーバーの応答を待機しています」が長い場合、ブラウザはただ待機するしかなくなります。

ですが、EarlyHintsでの読み込みを行うと、サーバーの応答を待機しながらも、必要なリソース読み込みを同時に行えます。以下フロー図の比較です。

実装方法について

概要

cloudflareのEarlyHintsサポートを利用します。
https://developers.cloudflare.com/cache/advanced-configuration/early-hints/

これは、Linkヘッダーを付与することで、指定したリソースのEarlyHintsを可能にします。
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Link

ref:
https://zenn.dev/kameoncloud/articles/6d2dec59232917

実際の実装

page.tsx

https://github.com/imamiya-masaki/earlyhints-playground/blob/main/src/app/page.tsx

検証のために、下記の読み込みを比較します。

<script async={true} src="https://imamiya-masaki.github.io/thirdparty-js/deliver/third-party2.js"/>
<script async={true} src="https://imamiya-masaki.github.io/thirdparty-js/deliver/third-party.js"/>

~/third-party.js,~/third-party2.jsに関しては、下記のコードを参照しています。
内容は同じもので名称だけ異なります。
https://github.com/imamiya-masaki/thirdparty-js/tree/main/deliver
また、今回Next.js 15 RCを利用しているため、React 19 RCのscriptタグの機能拡張を利用しています。こちらは、async={true}とすることで、headタグ内に、scriptタグを差し込みます。
https://ja.react.dev/reference/react-dom/components/script

下記のコードで意図的にサーバーの応答待機時間を伸ばします。

  function sleep(milliseconds: number) {
    return new Promise(resolve => setTimeout(resolve, milliseconds));
  }
  await sleep(6000)

next.config.mjs

https://github.com/imamiya-masaki/earlyhints-playground/blob/main/next.config.mjs

Next.jsでは、config.jsで、pathごとのHTTPヘッダーを設定できます。
https://nextjs.org/docs/pages/api-reference/next-config-js/headers

third-party.jsとheavy-image.jpgに対して、Linkヘッダーを付与します。

動きについて

EarlyHintsが有効になっているかどうかは、curlコマンドでも確認できます。

実際に、103ステータスが返ってきていることを確認できます。

次に、ページに訪問してみます。
third-party.jsthird-party2.jsは名称以外全て同じで、キャッシュの設定なども異なりませんが、third-party.jsの方は、ディスクキャッシュから取得され、ダウンロード時間が短くなっていることがわかります。

Streamingとの対比

Next.jsではEarlyHintsと似た恩恵を受けられる機能として、Streamingがあります。
https://zenn.dev/anpan/articles/b1bfedc4afcbfd#streaming
https://nextjs.org/learn/dashboard-app/streaming

こちらは、レンダリングが完了した部分のHTMLを素早く返し、
順次、ブラウザに返せる状態になったものからレスポンスします。
ブラウザからは長時間ダウンロードされているように見えます。

サーバーの応答待機時間に該当するような処理は、基本的にはbody内部の生成になります。
そのため、headタグだけであれば短時間で返すことができるため、Streaming + rel=preloadを利用すると、EarlyHintsに似た恩恵を受けることができます。
https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/rel/preload

Streaming

メリット

  • わざわざ特定のリソースを指定しなくても、サーバーの応答待機時間を有効活用できる
  • EarlyHintsが対応していないCDNでも利用可能

デメリット

  • App Routerでしか利用できない。(そのため Next.js 13以上)
  • エラーステータスを返せない

EarlyHints

メリット

  • EarlyHintsが利用可能な環境であれば、簡単に導入可能

デメリット

  • 利用する場合は、リソースを指定しないといけない。

私見

私の意見としては、Streamingの技術はEarlyHintsの恩恵以外も受けることができるため、
可能であればStreamingによる早期リソース取得の方が、より良いと考えています。
とはいえ、AppRouter自体がまだ普及しきれていないため、
サーバーの応答待機時間がボトルネックなサービスで、簡単にStreamingの導入が難しそうであればとりあえずEarlyHintsの有効化を行うのは有効な手段かと思います。

Discussion