0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Suspenseのフォールバックに構造化データを置くと、初期HTMLに2つ残る ─ 置き場所の決め方とcurlでの確かめ方

0
Posted at

この記事でできること

Next.jsのApp Routerで、ページの一部を<Suspense>で分けると、待っている間はfallbackに渡した要素が表示されます。このfallbackの中にJSON-LD(構造化データ)を置くと、本体が届いた後も、初期HTMLにはフォールバックのJSON-LDと本体のJSON-LDが両方残ります。

個人開発している技術記事キュレーションサービスDevPickでは、記事詳細ページのパンくずを<Suspense>で分けていて、フォールバック(2階層)と本体(3階層)の両方がBreadcrumbListを出していました。

この記事では、フォールバックに何を置いてよいかの決め方と、本番の初期HTMLで二重出力を確かめる手順をまとめます。対象はNext.js 16・React 19のApp Routerです。

ストリーミングSSRでフォールバックが残る仕組み

<Suspense>の中身が最初の送信に間に合わないと、サーバーはまずフォールバックをHTMLに書き出します。本体は準備ができた時点で、同じレスポンスの後ろの方に<div hidden>として追加で送られます。ブラウザではReactがJSで本体をフォールバックの位置へ移し、フォールバックを消します。

つまり、画面に見えるのは本体だけでも、サーバーが返したHTMLには両方が入っています。DevPickの記事詳細ページの初期HTMLは、修正前はこのような並びでした(中身は省略しています)。

<!--$?--><template id="B:0"></template>
<script type="application/ld+json">{"@type":"BreadcrumbList", ... 2階層 ...}</script>
<nav aria-label="パンくずリスト">トップ &gt; 記事タイトル</nav>
<!--/$-->
...
<div hidden id="S:0">
  <script type="application/ld+json">{"@type":"BreadcrumbList", ... 3階層 ...}</script>
  <nav aria-label="パンくずリスト">トップ &gt; カテゴリ &gt; 記事タイトル</nav>
</div>

B:0の位置にあるのがフォールバック、S:0の中にあるのが後から届いた本体です。JSを実行せずにHTMLを読むツールからは、BreadcrumbListが2つあるように見えます。

点検1:フォールバックがどのくらい出ているかを知る

フォールバックは「読み込みが遅いときだけ出るもの」と思いがちですが、待っているデータが最初の送信までに解決しなければ、毎回出ます。

  • <Suspense>の中で待っているデータが、ページの他の部分より遅く返ることが多いかを確認する
  • 開発環境はAPIが速く、フォールバックが一瞬で消えるので気づきにくい。本番のHTMLで確かめる
  • 本番のHTMLに<!--$?--><template id="B:0">があれば、その場所にフォールバックが書き出されている

点検2:フォールバックには見た目の要素だけを置く

フォールバックの中身は、本体に差し替えられた後も初期HTMLに残ります。そのため、ページに1つだけあるべきものは、フォールバックに置くと本体と二重になります。

  • JSON-LDはフォールバックに置かず、本体だけが出す
  • フォールバックは、本体と同じ形の見た目(DevPickではカテゴリを含まないパンくず)だけにする
  • フォールバック用のコンポーネントを本体からコピーして作った場合は、構造化データを出す処理まで一緒に持ってきていないか確認する

DevPickでは、フォールバック用のパンくずコンポーネントからJSON-LDを出す処理を消し、見た目のパンくずだけを描画するようにしました。

点検3:本番の初期HTMLで、構造化データの数を数える

二重出力は、JSで差し替える前のHTMLにしかありません。curlでサーバーが返したHTMLをそのまま取得して数えます。

curl -s https://example.com/articles/123 \
  | grep -o '"@type":"BreadcrumbList"' | wc -l
  • 結果が1なら、BreadcrumbListは1つだけ
  • 2以上なら、フォールバックと本体の両方が出している可能性が高い。<div hidden id="S:0">の前後のどちらにあるかを見て、どちらが出しているかを切り分ける
  • @typeの書き方(空白の有無など)は出力のしかたで変わるので、一度HTMLを目で見てから検索する文字列を決める

DevPickでは修正後、同じコマンドで記事詳細ページのBreadcrumbListが1つになったことを確認しています。残った1つは本体のもので、<div hidden id="S:0">の中にあります。

点検4:フォールバック単体のテストで、構造化データが無いことを固定する

画面の表示だけを確かめるテストでは、フォールバックが余計なJSON-LDを出していても通ってしまいます。

  • フォールバックのコンポーネントを単体で描画し、script[type="application/ld+json"]が0件であることを確かめる
  • 本体のコンポーネントでは、JSON-LDが1件だけで、条件に応じて階層の数が変わることを確かめる

まとめ

  • <Suspense>のフォールバックは、待っているデータが最初の送信に間に合わなければ毎回出る。本体に差し替えられた後も初期HTMLに残る
  • JSON-LDのように1ページに1つだけあるべきものは、フォールバックに置かず本体だけが出す
  • 二重出力はJSを実行した後の画面には出ないので、curlで取得した初期HTMLで数を確かめる
  • フォールバック単体のテストで、構造化データを出さないことを固定する

パンくずを<Suspense>で分けた経緯と、ほかの直し方を選ばなかった理由は、Zenn版に書いています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?