Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

© DMM.com 自己紹介
 2 名前
 清川航一 (𝕏 @kiyoshiro944)
 おしごと
 おもにフロントエンド(React/TypeScript) 
 バックエンドも書く(Golang) 
 所属
 
                   ポイントクラブチーム 
                   新卒2年目 
 
 
 


Slide 3

Slide 3 text

© DMM.com 前提・背景 
 3

Slide 4

Slide 4 text

© DMM.com 前提:用語の定義
 • SPA
 • ※人によって定義が違う😇
 • CSRしかしていないアプリ
 • CSR / SSR / SGを1つ以上組み合わせたアプリ
 • 純SPA
 • CSRしかしていないアプリ
 • 例:Create React AppやViteで作ったアプリ


Slide 5

Slide 5 text

© DMM.com • 2年くらい純SPAでフロントをつくっていた
 • パフォーマンス・SEOが必要になったため、今年の7月に2人で1ヶ月 かけ てNext.jsへ移行した
 • 「純SPAのままNext.jsに対抗する」方法も考えていた
 • そのアイデアを少し一般化してこの発表で供養
 
 背景:ポイントクラブでは


Slide 6

Slide 6 text

© DMM.com 純SPAの課題 
 6

Slide 7

Slide 7 text

© DMM.com (色々あるけど、今回取り上げるのは) 
 「ページによってmetaタグを切り替える」
 metaタグの例:
 • Next.jsではページごとにHTMLを生成するのが簡単
 /users → users.html
 /posts → posts.html
 
 • 純SPAだと、基本的にページによらずひとつのindex.htmlを使う
 /users → index.html
 /posts → index.html
 
 純SPAだとムズいこと


Slide 8

Slide 8 text

© DMM.com 純SPAの課題に直面する例
 󰞵Webアプリケーションのフロントエンドを開発することになりました
 󰢏とくにSEOや初回表示速度が重要なわけでもないので、純SPAでつくることに
 ✅開発も終盤にさしかかってそろそろリリースできるかと思った矢先、
 「SNSで共有されたときのために、
 ページによってmetaタグ変えたい」
 という要件がでてきました。。。
 ※󰢃最初からNext.jsを採用しておけというツッコミはNG 


Slide 9

Slide 9 text

© DMM.com 純SPAはブラウザでしか実行されないが、Next.jsだと、 Node.jsとブラウザの両方で実行される。
 例えば、useEffectの外でWeb APIにアクセスしている場合エラーがでる 
 (ポイントクラブで実際にNext.jsへの移行に 1ヶ月くらいかかった原因のひとつ😇)
 純SPAからNext.jsへの移行は案外大変


Slide 10

Slide 10 text

© DMM.com 純SPAのまま
 この課題を解決する方法を 見ていきましょう󰢏 
 10

Slide 11

Slide 11 text

© DMM.com ⚠注意点
 • 時間が足りなそうなので、細かい実装の話はしません
 • 気になる人はhttps://github.com/KoichiKiyokawa/spa-meta
 • AWSを使っていますがCloudflareとかのほうが簡単にできるかも
 • この発表は純SPAの使用を推奨するものではありません
 • あくまで回避策のお話
 


Slide 12

Slide 12 text

© DMM.com 「ページによってmetaタグを変えたい」の解像度を上げる
 • 動的か静的かの2種類に分類できる
 
 
 
 
 
 • 静的のほうがインフラ構成などが楽
 動的
 静的
 パターン数が多すぎて事前生成ができないのでリ クエスト時に実行する 
 
 例:/posts/1, /posts/2, …/posts/9999 
 でmetaタグを変える 
 パターン数が少なく事前生成できる 
 
 例:/aboutだけmetaタグを変える 


Slide 13

Slide 13 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 ?
 ?
 ?
 ?
 インフラ
 構築難度
 ?
 ?
 ?
 ?
 保守難度
 ?
 ?
 ?
 ?
 その他
 
 
 
 
 ※dynamic-rendering以外の名称は自分が適当に考えました

Slide 14

Slide 14 text

© DMM.com • デモ:https://dynamic-rendering.kiyoshiro.me/posts/123
 
 方法1:dynamic rendering(概要)


Slide 15

Slide 15 text

© DMM.com Lambda@Edge + CloudFront + S3の場合
 方法1:dynamic rendering(インフラ例)


Slide 16

Slide 16 text

© DMM.com Lambda@Edge + CloudFront + S3の場合
 方法1:dynamic rendering(インフラ例)
 キャッシュの有無に関わらず、 
 リクエストのたびに必ず実行される 
 • キャッシュヒットしなかったときだ け実行される
 • ここで返したレスポンスはキャッ シュされる


Slide 17

Slide 17 text

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


Slide 18

Slide 18 text

© DMM.com 😄GOOD
 • SEO:ページコンテンツも描画できる
 • 保守性:一度インフラを整えたら手を加える必要なし
 • アプリコードだけに集中できる
 方法1:dynamic rendering


Slide 19

Slide 19 text

© DMM.com 方法1:dynamic rendering
 😩BAD
 • 最初にインフラ構築するのが大変
 bot判定・ユーザーとbotでキャッシュの分離・ヘッドレスブラウザの実行etc… 
 • Google曰く、インフラが複雑になるので「回避策」
 • 回避策としてのダイナミック レンダリング | Google 検索セントラル
 • Next.jsとかのSSRと比べてレスポンスが遅くなりがち
 
 
 • Google Botへのレスポンスが遅くなると SEOに悪影響が(CoreWebVitals)
 • CloudFrontなどCDNのstale-while-revalidate機能 を使うと軽減できそう 
 
 ※キャッシュヒットしなかったとき

Slide 20

Slide 20 text

© DMM.com 補足:CDNのstale-while-revalidate機能
 キャッシュヒットしなかったときの比較(矢印の長さに注目)


Slide 21

Slide 21 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 ?
 ?
 ?
 インフラ
 構築難度
 ✕難
 ?
 ?
 ?
 保守難度
 ◎必要なし
 ?
 ?
 ?
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 


Slide 22

Slide 22 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 ?
 ?
 ?
 インフラ
 構築難度
 ✕難
 ?
 ?
 ?
 保守難度
 ◎必要なし
 ?
 ?
 ?
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 


Slide 23

Slide 23 text

© DMM.com 方法2:head-only SSR(概要)
 デモ:https://head-only-ssr.kiyoshiro.me/posts/123
 dist/index.html

Slide 24

Slide 24 text

© DMM.com 方法2:head-only SSR(コード)
 esbuildなどを使ってビルド時に埋め込むと S3 へのアクセスが省略できて良い

Slide 25

Slide 25 text

© DMM.com 方法2:head-only SSR(インフラ)
 • キャッシュヒットしなかったときだ け実行。レスポンスはキャッシュさ れる


Slide 26

Slide 26 text

© DMM.com 方法2:head-only SSR
 😄GOOD
 • dynamic renderingよりもインフラ構成が楽&レスポンスも早い
 😩BAD
 • 保守性:ページが増えるたびにエッジ関数に追記してデプロイする必要あり
 例:/posts/:idに加えて、/users/:idパスが増えたらエッジ関数に追記 
 • SEO:ページのコンテンツはレンダリングしない
 


Slide 27

Slide 27 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 エッジ関数でmeta タグを注入した HTMLを返却
 ?
 ?
 インフラ
 構築難度
 ✕難
 △微難
 ?
 ?
 保守難度
 ◎必要なし
 ✕大変
 ?
 ?
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 


Slide 28

Slide 28 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 エッジ関数でmeta タグを注入した HTMLを返却
 ?
 ?
 インフラ
 構築難度
 ✕難
 △微難
 ?
 ?
 保守難度
 ◎必要なし
 ✕大変
 ?
 ?
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 


Slide 29

Slide 29 text

© DMM.com 方法3:head-only SG
 ↑index.html
 ↓そこから生成したabout.html 
 例:/aboutページだけmetaタグを変えたい場合 
 インフラ
 /aboutでアクセスされたらabout.htmlを参照するよう に設定
 
 ビルド時
 /aboutページ用のabout.htmlを作る 
 (headタグだけ注入)


Slide 30

Slide 30 text

© DMM.com 方法3:head-only SG dist
 ├ index.html
 ├ …
 dist
 ├ index.html
 ├ about.html
 ├ …
 ビルド スクリプト実 行 metaタグ注入


Slide 31

Slide 31 text

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


Slide 32

Slide 32 text

© DMM.com 方法3:head-only SG
 😄GOOD
 • head-only SSRよりもインフラ構成が楽
 • /aboutを/about.htmlにルーティングするだけ
 😩BAD
 • 保守性:アプリコードとは別に、head注入するスクリプトを保持する必要あり
 • SEO:ページのコンテンツはレンダリングしない
 


Slide 33

Slide 33 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 エッジ関数でmeta タグを注入した HTMLを返却
 ビルド時にmetaタグを 注入したHTMLをつくる 
 ?
 インフラ
 構築難度
 ✕難
 △微難
 ◯簡単
 ?
 保守難度
 ◎必要なし
 ✕大変
 △普通
 
 ?
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 


Slide 34

Slide 34 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 エッジ関数でmeta タグを注入した HTMLを返却
 ビルド時にmetaタグを 注入したHTMLをつくる 
 ?
 インフラ
 構築難度
 ✕難
 △微難
 ◯簡単
 ?
 保守難度
 ◎必要なし
 ✕大変
 △普通
 
 ?
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 


Slide 35

Slide 35 text

© DMM.com ビルド時に、Bot用のレンダリングされたHTMLをヘッドレスブラウザで作成
 方法4:static prerendering
 リクエスト時ではなく 
 ビルド時に行う
 👆(再掲)dynamic renderingの図

Slide 36

Slide 36 text

© DMM.com 方法4:static prerendering
 ※細かい処理は省略

Slide 37

Slide 37 text

© DMM.com 方法4:static prerendering


Slide 38

Slide 38 text

© DMM.com 方法4:static prerendering
 😄GOOD
 • head-only SGと同様、インフラ構成は楽
 • Botからのアクセス時に/aboutへのリクエストを/about.htmlにルーティングするだ け
 • SEO:ページコンテンツもレンダリングできる
 😩BAD
 • 保守性:アプリコードとは別で、ヘッドレスブラウザを実行するスクリプトを保守する必 要あり
 • ビルド時間:ヘッドレスブラウザを動かす分、時間がかかる
 


Slide 39

Slide 39 text

© DMM.com 4種類の方法一覧
 
 動的(リクエスト時に実行) 
 静的(ビルド時に実行) 
 方法
 dynamic rendering
 head-only SSR
 head-only SG
 static prerendering
 概要
 botからアクセスされたとき だけヘッドレスブラウザでレ ンダリングしたHTMLを返却
 エッジ関数でmeta タグを注入した HTMLを返却
 ビルド時にmetaタグを 注入したHTMLをつくる 
 ビルド時にヘッドレスブ ラウザでHTMLを生成す る
 インフラ
 構築難度
 ✕難
 △微難
 ◯簡単
 ◯簡単
 保守難度
 ◎必要なし
 ✕大変
 △普通
 △普通
 その他
 😄ページコンテンツも レンダリング
 😩不安定で遅い
 
 
 😄ページコンテンツもレ ンダリング
 


Slide 40

Slide 40 text

© DMM.com まとめ 
 40

Slide 41

Slide 41 text

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


Slide 42

Slide 42 text

© DMM.com • 面倒なので、最初からNext.jsを採用しよう
 • ページによってmetaタグを切り替えられる以外にもメリット多い
 • zero config
 • viewportに入ったリンクのprefetch
 • 画像/フォント/外部scriptの読み込み最適化
 • etc…
 とはいえ・・・


Slide 43

Slide 43 text

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