$30 off During Our Annual Pro Sale. View Details »

純SPAでNext.jsに対抗する 〜ページによってmetaタグを切り替える編〜

kiyoshiro
September 12, 2023

純SPAでNext.jsに対抗する 〜ページによってmetaタグを切り替える編〜

純SPAではNext.jsとは異なり、基本的にひとつのindex.htmlを用いるため、ページによってmetaタグを切り替えるのが難しい。このスライドでは、CloudFrontやLambda@Edgeなどを使って純SPAでもページによってmetaタグを切り替える方法を4つ紹介する。

kiyoshiro

September 12, 2023
Tweet

Other Decks in Programming

Transcript

  1. 純SPAでNext.jsに対抗する
    〜ページによってmetaタグを切り替える編〜
    清川 航⼀

    View Slide

  2. © DMM.com
    自己紹介

    2
    名前
 清川航一 (𝕏 @kiyoshiro944)

    おしごと
 おもにフロントエンド(React/TypeScript) 

    バックエンドも書く(Golang) 

    所属
 

                      ポイントクラブチーム 

                      新卒2年目 




    View Slide

  3. © DMM.com
    前提・背景

    3

    View Slide

  4. © DMM.com
    前提:用語の定義

    • SPA

    • ※人によって定義が違う😇

    • CSRしかしていないアプリ

    • CSR / SSR / SGを1つ以上組み合わせたアプリ

    • 純SPA

    • CSRしかしていないアプリ

    • 例:Create React AppやViteで作ったアプリ


    View Slide

  5. © DMM.com
    • 2年くらい純SPAでフロントをつくっていた

    • パフォーマンス・SEOが必要になったため、今年の7月に2人で1ヶ月 かけ
    てNext.jsへ移行した

    • 「純SPAのままNext.jsに対抗する」方法も考えていた

    • そのアイデアを少し一般化してこの発表で供養


    背景:ポイントクラブでは


    View Slide

  6. © DMM.com
    純SPAの課題

    6

    View Slide

  7. © DMM.com
    (色々あるけど、今回取り上げるのは) 

    「ページによってmetaタグを切り替える」

    metaタグの例:

    • Next.jsではページごとにHTMLを生成するのが簡単

    /users → users.html

    /posts → posts.html


    • 純SPAだと、基本的にページによらずひとつのindex.htmlを使う

    /users → index.html

    /posts → index.html


    純SPAだとムズいこと


    View Slide

  8. © DMM.com
    純SPAの課題に直面する例

    󰞵Webアプリケーションのフロントエンドを開発することになりました

    󰢏とくにSEOや初回表示速度が重要なわけでもないので、純SPAでつくることに

    ✅開発も終盤にさしかかってそろそろリリースできるかと思った矢先、

    「SNSで共有されたときのために、

    ページによってmetaタグ変えたい」

    という要件がでてきました。。。

    ※󰢃最初からNext.jsを採用しておけというツッコミはNG

    View Slide

  9. © DMM.com
    純SPAはブラウザでしか実行されないが、Next.jsだと、
    Node.jsとブラウザの両方で実行される。

    例えば、useEffectの外でWeb APIにアクセスしている場合エラーがでる

    (ポイントクラブで実際にNext.jsへの移行に
    1ヶ月くらいかかった原因のひとつ😇)

    純SPAからNext.jsへの移行は案外大変


    View Slide

  10. © DMM.com
    純SPAのまま

    この課題を解決する方法を
    見ていきましょう󰢏

    10

    View Slide

  11. © DMM.com
    ⚠注意点

    • 時間が足りなそうなので、細かい実装の話はしません

    • 気になる人はhttps://github.com/KoichiKiyokawa/spa-meta

    • AWSを使っていますがCloudflareとかのほうが簡単にできるかも

    • この発表は純SPAの使用を推奨するものではありません

    • あくまで回避策のお話


    View Slide

  12. © DMM.com
    「ページによってmetaタグを変えたい」の解像度を上げる

    • 動的か静的かの2種類に分類できる






    • 静的のほうがインフラ構成などが楽

    動的
 静的

    パターン数が多すぎて事前生成ができないのでリ
    クエスト時に実行する 


    例:/posts/1, /posts/2, …/posts/9999 

    でmetaタグを変える 

    パターン数が少なく事前生成できる 


    例:/aboutだけmetaタグを変える 


    View Slide

  13. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 ?
 ?
 ?
 ?

    インフラ

    構築難度

    ?
 ?
 ?
 ?

    保守難度
 ?
 ?
 ?
 ?

    その他
 
 
 
 

    ※dynamic-rendering以外の名称は自分が適当に考えました

    View Slide

  14. © DMM.com
    • デモ:https://dynamic-rendering.kiyoshiro.me/posts/123


    方法1:dynamic rendering(概要)


    View Slide

  15. © DMM.com
    Lambda@Edge + CloudFront + S3の場合

    方法1:dynamic rendering(インフラ例)


    View Slide

  16. © DMM.com
    Lambda@Edge + CloudFront + S3の場合

    方法1:dynamic rendering(インフラ例)

    キャッシュの有無に関わらず、 

    リクエストのたびに必ず実行される 

    • キャッシュヒットしなかったときだ
    け実行される

    • ここで返したレスポンスはキャッ
    シュされる


    View Slide

  17. © DMM.com
    方法1:dynamic rendering(アプリコード)


    View Slide

  18. © DMM.com
    😄GOOD

    • SEO:ページコンテンツも描画できる

    • 保守性:一度インフラを整えたら手を加える必要なし

    • アプリコードだけに集中できる

    方法1:dynamic rendering


    View Slide

  19. © DMM.com
    方法1:dynamic rendering

    😩BAD

    • 最初にインフラ構築するのが大変

    bot判定・ユーザーとbotでキャッシュの分離・ヘッドレスブラウザの実行etc… 

    • Google曰く、インフラが複雑になるので「回避策」

    • 回避策としてのダイナミック レンダリング | Google 検索セントラル

    • Next.jsとかのSSRと比べてレスポンスが遅くなりがち



    • Google Botへのレスポンスが遅くなると SEOに悪影響が(CoreWebVitals)

    • CloudFrontなどCDNのstale-while-revalidate機能 を使うと軽減できそう 


    ※キャッシュヒットしなかったとき

    View Slide

  20. © DMM.com
    補足:CDNのstale-while-revalidate機能

    キャッシュヒットしなかったときの比較(矢印の長さに注目)


    View Slide

  21. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    ?
 ?
 ?

    インフラ

    構築難度

    ✕難
 ?
 ?
 ?

    保守難度
 ◎必要なし
 ?
 ?
 ?

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 


    View Slide

  22. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    ?
 ?
 ?

    インフラ

    構築難度

    ✕難
 ?
 ?
 ?

    保守難度
 ◎必要なし
 ?
 ?
 ?

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 


    View Slide

  23. © DMM.com
    方法2:head-only SSR(概要)

    デモ:https://head-only-ssr.kiyoshiro.me/posts/123

    dist/index.html

    View Slide

  24. © DMM.com
    方法2:head-only SSR(コード)

    esbuildなどを使ってビルド時に埋め込むと S3
    へのアクセスが省略できて良い

    View Slide

  25. © DMM.com
    方法2:head-only SSR(インフラ)

    • キャッシュヒットしなかったときだ
    け実行。レスポンスはキャッシュさ
    れる


    View Slide

  26. © DMM.com
    方法2:head-only SSR

    😄GOOD

    • dynamic renderingよりもインフラ構成が楽&レスポンスも早い

    😩BAD

    • 保守性:ページが増えるたびにエッジ関数に追記してデプロイする必要あり

    例:/posts/:idに加えて、/users/:idパスが増えたらエッジ関数に追記 

    • SEO:ページのコンテンツはレンダリングしない


    View Slide

  27. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    エッジ関数でmeta
    タグを注入した
    HTMLを返却

    ?
 ?

    インフラ

    構築難度

    ✕難
 △微難
 ?
 ?

    保守難度
 ◎必要なし
 ✕大変
 ?
 ?

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 


    View Slide

  28. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    エッジ関数でmeta
    タグを注入した
    HTMLを返却

    ?
 ?

    インフラ

    構築難度

    ✕難
 △微難
 ?
 ?

    保守難度
 ◎必要なし
 ✕大変
 ?
 ?

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 


    View Slide

  29. © DMM.com
    方法3:head-only SG

    ↑index.html

    ↓そこから生成したabout.html 

    例:/aboutページだけmetaタグを変えたい場合 

    インフラ

    /aboutでアクセスされたらabout.htmlを参照するよう
    に設定


    ビルド時

    /aboutページ用のabout.htmlを作る

    (headタグだけ注入)


    View Slide

  30. © DMM.com
    方法3:head-only SG
    dist

    ├ index.html

    ├ …

    dist

    ├ index.html

    ├ about.html

    ├ …

    ビルド
    スクリプト実

    metaタグ注入


    View Slide

  31. © DMM.com
    方法3:head-only SG


    View Slide

  32. © DMM.com
    方法3:head-only SG

    😄GOOD

    • head-only SSRよりもインフラ構成が楽

    • /aboutを/about.htmlにルーティングするだけ

    😩BAD

    • 保守性:アプリコードとは別に、head注入するスクリプトを保持する必要あり

    • SEO:ページのコンテンツはレンダリングしない


    View Slide

  33. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    エッジ関数でmeta
    タグを注入した
    HTMLを返却

    ビルド時にmetaタグを
    注入したHTMLをつくる 

    ?

    インフラ

    構築難度

    ✕難
 △微難
 ◯簡単
 ?

    保守難度
 ◎必要なし
 ✕大変
 △普通


    ?

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 


    View Slide

  34. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    エッジ関数でmeta
    タグを注入した
    HTMLを返却

    ビルド時にmetaタグを
    注入したHTMLをつくる 

    ?

    インフラ

    構築難度

    ✕難
 △微難
 ◯簡単
 ?

    保守難度
 ◎必要なし
 ✕大変
 △普通


    ?

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 


    View Slide

  35. © DMM.com
    ビルド時に、Bot用のレンダリングされたHTMLをヘッドレスブラウザで作成

    方法4:static prerendering

    リクエスト時ではなく 

    ビルド時に行う

    👆(再掲)dynamic renderingの図

    View Slide

  36. © DMM.com
    方法4:static prerendering

    ※細かい処理は省略

    View Slide

  37. © DMM.com
    方法4:static prerendering


    View Slide

  38. © DMM.com
    方法4:static prerendering

    😄GOOD

    • head-only SGと同様、インフラ構成は楽

    • Botからのアクセス時に/aboutへのリクエストを/about.htmlにルーティングするだ
    け

    • SEO:ページコンテンツもレンダリングできる

    😩BAD

    • 保守性:アプリコードとは別で、ヘッドレスブラウザを実行するスクリプトを保守する必
    要あり

    • ビルド時間:ヘッドレスブラウザを動かす分、時間がかかる


    View Slide

  39. © DMM.com
    4種類の方法一覧

    
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 

    方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering

    概要
 botからアクセスされたとき
    だけヘッドレスブラウザでレ
    ンダリングしたHTMLを返却

    エッジ関数でmeta
    タグを注入した
    HTMLを返却

    ビルド時にmetaタグを
    注入したHTMLをつくる 

    ビルド時にヘッドレスブ
    ラウザでHTMLを生成す
    る

    インフラ

    構築難度

    ✕難
 △微難
 ◯簡単
 ◯簡単

    保守難度
 ◎必要なし
 ✕大変
 △普通
 △普通

    その他
 😄ページコンテンツも
    レンダリング

    😩不安定で遅い

    
 
 😄ページコンテンツもレ
    ンダリング


    View Slide

  40. © DMM.com
    まとめ

    40

    View Slide

  41. © DMM.com
    • 純SPAでもLambda@Edgeなどを使えばページごとにmetaタグを切り替える
    ことができる

    まとめ


    View Slide

  42. © DMM.com
    • 面倒なので、最初からNext.jsを採用しよう

    • ページによってmetaタグを切り替えられる以外にもメリット多い

    • zero config

    • viewportに入ったリンクのprefetch

    • 画像/フォント/外部scriptの読み込み最適化

    • etc…

    とはいえ・・・


    View Slide

  43. © DMM.com
    ご清聴ありがとうございました

    43

    View Slide