Slide 1

Slide 1 text

©MIXI WordPressのヘッドレス運用化 鈴木 雅也 (Masaya Suzuki) Vantageスタジオminimo事業部 開発グループバックエンド開発チーム 樋口 鉄朗 (Tetsuro Higuchi) Vantageスタジオminimo事業部 開発グループWebフロントエンド開発チーム 〜 minimo roomのJamstack構成移行プロジェクト 〜

Slide 2

Slide 2 text

©MIXI 自己紹介 2

Slide 3

Slide 3 text

©MIXI ● 鈴木 雅也 (Masaya Suzuki) ● 経歴 ○ 2019年: 株式会社ミクシィ (現・株式会社MIXI) 新卒入社 ○ 2019〜2021年5月: モンスターストライク等のデータ分析やデータ基盤の構築・運用を担当 ○ 2021年6月〜: minimoでサーバーサイドの開発を中心にインフラの構築・運用も担当 ● 著書 ○ 数式をプログラムするってつまりこういうこと https://www.socym.co.jp/book/1206 ○ データサイエンス100本ノック構造化データ加工編ガイドブック https://www.socym.co.jp/book/1356 自己紹介 3

Slide 4

Slide 4 text

©MIXI ● 樋口 鉄朗 (Tetsuro Higuchi) ● 経歴 ○ 2019年 前職へ新卒入社 ■ デジタルコンテンツの販売プラットフォームの事業部でフロントエンドエンジニア ■ 途中から新規事業部へ、3ヶ月毎にプロジェクトが立ち消えするのを3度経験して転職 ○ 2021年株式会社ミクシィ (現・株式会社MIXI) へJOIN ■ minimo 3年目突入 フロントエンド開発グループにてフロントエンドエンジニア ● 趣味 ○ バイク、ロードバイク、一人旅、アニメ鑑賞、VRSNS、他 自己紹介 4

Slide 5

Slide 5 text

©MIXI minimoやminimo roomについて 5

Slide 6

Slide 6 text

©MIXI ● 2024年1月で10周年を迎えたサービス ● 美容師やネイリスト、アイデザイナー等を予約・検索できる アプリ ● サロンスタッフと直接やり取りができる minimoについて https://minimodel.jp/about 6

Slide 7

Slide 7 text

©MIXI ● minimoの公式Webメディア ● 2022年5月リリース ● 現役美容師や元ネイリスト、美容系インフルエンサーなど の経験を持つ個性豊かなメンバーが執筆 ● トレンドの髪型からネイル紹介まで様々な最新の美容トレ ンドや美容知識についての記事を掲載 minimo roomについて https://minimodel.jp/room/ 7

Slide 8

Slide 8 text

©MIXI Jamstack移行の経緯 8

Slide 9

Slide 9 text

©MIXI ● minimo roomはWordPressで構築されている ● 2021年よりフロントエンド開発チームが創設 ● 見た目の機能開発が多いminimo roomではフロントエンド開発チームだけで開発したい ● 開発チーム内で管理できる技術スタック内でなるべく運用したい ● 既に数百記事が公開されている事から、SaaS等への移行はコストとリスクが高い ● 外部からもライターさんを雇っている以上、エディタの変更は避けたい ○ WordPressでの執筆に慣れているライターさんが多い ○ WordPress以外のエディタにすると、ライターさん募集時にそれが障害となる可能性がある → WordPressは残しておきたい・・・! → 手法を検討 フロントエンドを分離 9

Slide 10

Slide 10 text

©MIXI Jamstack 10 リクエスト HTML等返却 データ取得 クライアント データベース Jamstack ストレージ (AWS S3等) 事前にビルド・デプロイ

Slide 11

Slide 11 text

©MIXI バックエンド フロントエンド ヘッドレス化、フロントエンド分離後 11 リクエスト HTML等返却 データ取得 ブラウザ データベース フロントエンド-バックエンド間は APIでデータをやり取り 定期的にビルドされるので、記事は最新 Next.jsが事前にHTMLをビルドしておくので レスポンスが早くなる APIでデータのみを提供する CMSを ヘッドレスCMSと呼ぶ

Slide 12

Slide 12 text

©MIXI ● メリット ○ 技術領域の分担が比較的簡単に ○ 見た目に関わる領域をWordPressから切り離せる ○ 編集画面などの管理機能をWordPressに残せる ○ 速度向上 ● デメリット ○ 管理するサーバー(Next.js)が増える ■ S3等へ静的デプロイを行うのであればその限りではない ○ 障害箇所の切り分け複雑度が増加 → WordPressをヘッドレスCMSとして運用し、Next.jsをフロントエンドとして利用する、フロントエンドを分離す る = Jamstack化 することに Jamstack化、ヘッドレス化、フロントエンド分離 12

Slide 13

Slide 13 text

©MIXI ● 技術的課題とその解決策 ○ WordPress REST APIとの戦い ○ 強いCSSとの戦い ○ Next.jsのキャッシュ機構との戦い ● フロントエンド分離のためのインフラ ○ 概要 ○ フロントエンド分離未対応ページへのルーティング ○ まとめ 目次 13

Slide 14

Slide 14 text

©MIXI 技術的課題とその解決策 14

Slide 15

Slide 15 text

©MIXI ● Next.js ● TypeScript ● GraphQL ● urql ● Linaria ● Vitest ● Storybook ● ESLint ● Prettier など。 15 フロントエンドで最終的に導入されたライブラリ・フレームワーク等

Slide 16

Slide 16 text

©MIXI WordPress REST APIとの戦い 16

Slide 17

Slide 17 text

©MIXI ● WordPressからはREST APIでデータが取れる ● ドキュメントがOpenAPI Specificationに則っていない ○ クライアントの自動生成が出来ない ○ WordPressのAPI Reference自体の検索性や使い勝手に難がある ○ 一応、wp-typesというTypeScript向けの型ライブラリはある ● WordPress REST APIの設計上、関連するデータ毎に単件取得APIを叩く必要がある ○ APIから返却される記事データに付随するタグやカテゴリーはIDしか含まれない ○ この仕様により、記事を表示するページを表示する為には下記APIを全部叩く必要がある ■ 記事単件取得API ■ タグ単件取得API ×記事に紐づくタグの件数分 ■ カテゴリ単件取得API ×記事に紐づくカテゴリの件数分 ■ 著者単件取得API ■ 画像単件取得API ○ 記事一覧取得APIも同様の設計 ● WordPress REST APIと問題点 17

Slide 18

Slide 18 text

©MIXI WordPress REST APIでデータを取得する場合のイメージ 18 1件の記事に付き4種×個数分・・・ 10件記事の情報取ろうと思ったら、最低でも約 50回もAPI叩く必要あるの!?? 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称 記事 ● タイトル ● 投稿日時 ● 抜粋文章 ● 著者 ● カテゴリ ● タグ ● アイキャッチ画像 著者 ● 名前 ● アバター画像 タグ ● 名称 カテゴリ ● 名称 添付画像 ● 画像URL ● ALT 添付画像 ● 画像URL ● ALT カテゴリ ● 名称 カテゴリ ● 名称 タグ ● 名称 タグ ● 名称

Slide 19

Slide 19 text

©MIXI そこでGraphQL 19 ● Facebook(現Meta)が中心に 開発を進めているAPI向けクエ リ言語 ● 必要なデータをクエリ化して1 回で問い合わせ ● 型システムがある query PostList($limit: 10) { posts(where: { last: $limit }) { nodes { title date description author { name attachedImage { src } } tags { nodes { name } categories { nodes { name } attachedImage { src } } } } 先程の図であれば、こんな感じのクエリになる これ1個で10件の記事と それに付随する情報全部取れる

Slide 20

Slide 20 text

©MIXI そこでGraphQL 20 ● WordPressは標準ではGraphQLに対応していない ● WordPress REST APIと同じ内容をGraphQLで取得できるようにする OSSのWordPressプラグインに、「WPGraphQL」がある ● https://github.com/wp-graphql/wp-graphql ● スター数は約3600 ● 主要なプラグインを組み合わせて使えるよう対応したWPGraphQL用プラ グインも多数存在 ● WordPress管理画面上からGraphQL IDEが使える ● 認証有無の設定なども詳しく設定できる ● 若干の不具合はあった ○ マルチバイト文字のファイル名を使ったテーマファイルがあると GraphQLスキーマが壊れる不具合 ■ PR送って直した ○ 記事本文のHTMLはGraphQLでデータ取ろうとするとエラーが発生 ■ 記事本文の取得はWordPress REST APIを使うことに

Slide 21

Slide 21 text

©MIXI GraphQLのFragmentを活用 21 ● Fragmentの活用 ○ コンポーネントのPropsのような利用箇所に対応するFragmentを定義 ■ FragmentをQuery上で組み合わせれば、必要最低限の情報だけをリクエストできる ■ Fragmentを書き換えれば自動的にQueryも書き換え ■ Fragment Maskingで不要な情報が他のコンポーネントに漏洩しないようにできる query Hoge { posts { nodes { id title date excerpt } } } fragment FugaA on Post { id title excerpt } query Hoge { posts { nodes { id …FugaA …FugaB } } } fragment FugaB on Post { id title date }

Slide 22

Slide 22 text

©MIXI コンポーネントで使いたいデータが増えても、fragment書き換えだけでOK 22 const { data } = await client.query(HogeDocument, {}); const posts = data.posts.node ?? []; const fugaA = getFragmentData(FugaAFragmentDoc, posts); console.log(posts); // [{ id: “xxx” }] console.log(fugaA); // [{ id: “xxx”, title “piyo”, excerpt: “hogefugapiyo”, status: “PUBLISH” }] fragment FugaA on Post { id title excerpt status } query Hoge { posts { nodes { id …FugaA } } }

Slide 23

Slide 23 text

©MIXI Fragment Maskingを使った場合、関数を使ってfragmentを取得する必要がある 23 const { data } = await client.query(HogeDocument, {}); const posts = data.posts.node ?? []; const fugaA = getFragmentData(FugaAFragmentDoc, posts); console.log(posts); // [{ id: “xxx” }] console.log(fugaA); // [{ id: “xxx”, title “piyo”, excerpt: “hogefugapiyo” }] fragment FugaA on Post { id title excerpt } query Hoge { posts { nodes { id …FugaA } } }

Slide 24

Slide 24 text

©MIXI Fragment Collocationの採用 24 fragment FugaA on Post { id title excerpt } query Hoge { posts { nodes { id …FugaA …FugaB } } } fragment FugaB on Post { id title date } ● app ○ post ■ page.tsx ■ query.gql ● components ○ FugaA ■ index.tsx ■ fragment.gql ○ FugaB ■ index.tsx ■ fragment.gql fragmentファイルの位置を、 使用するファイルと同じ階層に設置 (=Collocate)

Slide 25

Slide 25 text

©MIXI まとめ 25 ● GraphQLはエンティティが複雑に絡み合うヘッドレスCMSにおいては非常に便利 ● Fragment Collocationを活用することでコンポーネント分割時の運用開発も便利に

Slide 26

Slide 26 text

©MIXI 強いCSSとの戦い 26

Slide 27

Slide 27 text

©MIXI ● WordPressから返却される記事本文は、執筆したHTMLがそのままURIエンコードされたもの ● 記事ページだけはWordPressテーマのCSSを使用する必要がある ○ これはCSS設計やクラス命名などを負債として引き継ぐ必要があることを意味する ● 採用しているWordPressテーマのCSS設計が非常に悪い ○ SMACSSやFLOCSSと言ったCSS命名規則は採用されていない ○ HTMLタグが直接セレクタとして指定されている ○ :not擬似クラスの極多用 ○ !importantの極多用 ○ 合計1万行を超える手実装+PHPによる文字列結合生成のCSS達 採用したWordPressテーマによる負債の影響 27

Slide 28

Slide 28 text

©MIXI 採用したWordPressテーマのCSS例 28 .post h3.has-regular-font-size, .post h3:not([class^='is-style-heading-custom-']):not([class*='is-style-heading-custom-']):not(.css-no2):not(.rankh3):not(.post-card-title):not(#reply-title), .h3modoki, .step-title { font-size: 19px; line-height: 27px; } 「.post h3」なセレクタに対してnotセレクタが6個くっついてる .has-cyan-bluish-gray-color { color: var(--wp--preset--color--cyan-bluish-gray) !important; } .has-white-color { color: var(--wp--preset--color--white) !important; } .timeline > li .cardbox.kanren { background-color: transparent; margin-bottom: 10px; margin-top: 0 !important; } .custom-search-box-tpl-default .cs-text-input { padding-left: 25px !important; padding-right: 25px !important; padding-top: 10px !important; padding-bottom: 10px !important; } どこでもかんでもみんな〜!importantが付いているよ〜

Slide 29

Slide 29 text

©MIXI ● 先述の通り、WordPressから直接CSSを取り寄せて全体にインポートするとあらゆる全てのページが汚 染される ● あまりに強すぎるセレクタで、CSS in JSによるスコープドCSSも上書きされる ● コンポーネントに区切っても、Next.jsのRouter Cache機能のせいで他のページにもCSSが漏洩 ● これはglobal stylingを使っても、@importなどを使ってもそうなる ● 1つ1つ潰していたらキリがない ● Shadow DOMやiframe等を使うとSEO上よろしくない ● せめてページ遷移する時にリロードし直してくれたら・・・・! スコープドCSSがWordPressにやられてしまう問題 29

Slide 30

Slide 30 text

©MIXI ● 先述の通り、WordPressから直接CSSを取り寄せて全体にインポートするとあらゆる全てのページが汚 染される ● あまりに強すぎるセレクタで、CSS in JSによるスコープドCSSも上書きされる ● コンポーネントに区切っても、Next.jsのRouter Cache機能のせいで他のページにもCSSが漏洩 ● これはglobal stylingを使っても、@importなどを使ってもそうなる ● 1つ1つ潰していたらキリがない ● Shadow DOMやiframe等を使うとSEO上よろしくない ● せめてページ遷移する時にリロードし直してくれたら・・・・! スコープドCSSがWordPressにやられてしまう問題 30

Slide 31

Slide 31 text

©MIXI ● グループレイアウトを分けると、レイアウトを跨いだ場合ページ全体が再読み込みされる ○ > Navigating across multiple root layouts will cause a full page load (as opposed to a client-side navigation). ○ > (google翻訳)複数のルート レイアウト間を移動すると、(クライアント側のナビゲーションとは対照的 に) ページ全体が読み込まれます。 ○ https://nextjs.org/docs/app/building-your-application/routing/route-groups ● 本来は、ヘッダーやフッターが切り替わるなど、レイアウトがガッツリ変わるようなページを跨いだ場合に 便利な機能 ● この機能を活用 グループレイアウトを分ける 31

Slide 32

Slide 32 text

©MIXI ● 記事を描画するページでのみWordPressテーマのCSSが読み込まれるように ● 記事ページ内のCSSによるレイアウト崩れには、対WordPressテーマ用リセットCSSを何行か追加するこ とである程度駆逐に成功 ● 残念ながら完全な形にするにはやはりCSSを丸ごと作り直すしかない・・・ 結果・まとめ 32 トップページ 記事ページ リンクホバーのアンダースコアが 残ってしまっている

Slide 33

Slide 33 text

©MIXI Next.jsのキャッシュ機構との戦い 33

Slide 34

Slide 34 text

©MIXI ● 2022年10月25日リリースのNext.js 13.0より、Betaリリース ○ https://nextjs.org/blog/next-13#new-app-directory-beta ● 2023年05月04日リリースのNext.js 13.4より、Stable ○ https://nextjs.org/blog/next-13-4#nextjs-app-router ● 主な新機能 ○ React Server Componentへの対応 ■ サーバーサイドでコンポーネントを構築する事でクライアントでのjs実行量を最小限に抑える ○ Layoutコンポーネント ■ 従来の「_document.tsx」や「_app.tsx」でやっていたような機能を分離 ■ ルート単位で共通化したコンポーネントを設置したり、metaタグの設定などを共有できる ○ fetch ■ Next.jsが拡張したfetchAPIが登場 ■ fetchした内容のキャッシュをすることで、余分な重複リクエストを削減する ○ OGP画像の動的ビルドサポート ○ 他多数 Next.js App Router 34 開発開始後、間もなく Stableになったので、 知見蓄積の為に積極的に採用

Slide 35

Slide 35 text

©MIXI Next.jsのビルド方式 35 ● Pages Routerでは ○ 「getStaticProps」か「getServerSideProps」でSSGとSSRが区別されてた ● Apps Routerでは ○ 場合に応じてNext.jsがよしなにしてくれる ○ SSG → Static Rendering(以後、SRと記載) ○ SSR → Dynamic Rendering(以後、DRと記載)

Slide 36

Slide 36 text

©MIXI ● 基本SRになる想定で実装しているのに・・・・ ● レスポンスがWordPress時代より遅い ● サーバー増強してもまだ遅い ● 終いにはHTTP 504 Gateway Timeout ● どうやら途中に挟んでいるCDNにはキャッシュされていないようだ・・・ 36 リリース直後から問題が発生

Slide 37

Slide 37 text

©MIXI CDNとの組み合わせにおいてはCache-Controlヘッダーが重要になる 37 ● CDNとしてAWS CloudFrontを採用 ○ CloudFrontはキャッシュ時間はCache-ControlヘッダーやExpiresヘッダーを見て決める ○ 無ければデフォルト設定値を適用 ● Next.jsの出力したCache-Controlは「private, no-cache, no-store, max-age=0, must-revalidate」 ○ これはCDNにもクライアントにも絶対にキャッシュさせない設定

Slide 38

Slide 38 text

©MIXI 実はCache-ControlヘッダーもNext.jsが操っている 38 > You cannot set Cache-Control headers in next.config.js for pages or assets, as these headers will be overwritten in production to ensure that responses and static assets are cached effectively. > (Google翻訳)next.config.js でページまたはアセットの Cache-Control ヘッダーを設定することはでき ません。これらのヘッダーは、応答と静的アセットが効果的にキャッシュされるように本番環境で上書きさ れるためです。 え、説明これだけ? https://nextjs.org/docs/app/api-reference/next-config-js/headers#cache-control

Slide 39

Slide 39 text

©MIXI 少なくとも下記パターンがある 1. private, no-cache, no-store, max-age=0, must-revalidate 2. s-maxage=[n], stale-while-revalidate ※nは任意の整数 3. no-store, must-revalidate 4. public, max-age=31536000, immutable ● どのパターンになるかは種々の設定やオプションの相互作用により最終的にNext.jsが決定する ● dev mode中は必ず「1」のパターンで配信されるので、next serveするまでどのcache-controlになるか分 からない ● Etagは吐くので、CDNを挟まなければ4以外のパターンなら検証してNextがビルドした最新のデータが表 示 39 Next.jsのCache-controlヘッダーの挙動

Slide 40

Slide 40 text

©MIXI 少なくとも下記パターンがある 1. private, no-cache, no-store, max-age=0, must-revalidate 2. s-maxage=[n], stale-while-revalidate ※nは任意の整数 3. no-store, must-revalidate 4. public, max-age=31536000, immutable ● 原因 ○ 記事一覧系のページにて、クエリパラメータを使用してページングをしていた ○ クエリパラメータを使うと、自動でDRに切り替わる ○ DRに切り替わると、Cache-Controlヘッダーのパターンは「1」が出力される 一覧ページを見る度にビルドが走るし、CDNにキャッシュされないので、非常に重くなっていた 40 今回の不具合の原因

Slide 41

Slide 41 text

©MIXI 具体例 41 export const revalidate = 1000; export const HogePage = ({ searchParams }) => { return

{searchParams.q}の検索結果

} export default HogePage; この仕様はカーソルベースのページングを行うリストページやクエリパラメータで検索ワードを指定するよう な検索ページでは一切キャッシュできない事を意味する 例: /commits/main/?after=hogefuga 例: /search?q=hoge

Slide 42

Slide 42 text

©MIXI ● 下記パターンは原則出来ない ○ Next.jsは全部DRにして、CDNではキャッシュさせる ○ クエリパラメータ使用ページを強制的にキャッシュ対象にする ○ Next.jsのキャッシュ時間は10分にして、CDNは12時間にしてno-cacheで常に再検証 ● ただし、どうしてもという場合は、middlewareで書き換えできる ○ ただしキャッシュヘッダーとNext.js側の挙動の乖離による副作用でアクセス不能になる可能性も高く なるのでおすすめはしない ● そのヘッダーがなぜ出力されているかを調べて、根本対処するべき 42 CDN挟む時どうしたらいいの? export const middleware = (request: NextRequest) => { const response = NextResponse.next({ request: { headers: new Headers(request.headers), }, }); response.headers.set(CACHE_CONTROL_HEADER, ”no-cache, s-max-age=3600”); return response; };

Slide 43

Slide 43 text

©MIXI ● Next.jsのキャッシュ機構は非常に複雑。よくドキュメントを読もう ● Next.jsのレールに乗っかろう。CDNとNext.jsのキャッシュ関係は、一蓮托生と捉えよ ● Next.jsが出力したCache-Controlヘッダーが意図したものか、Pageリリース前に検査する ● 意図していないのであれば、出力された理由を調べて根本対処しよう ○ キャッシュでここまで疲弊するならシンプルな Remixで良かったなぁ・・・ ○ RemixはSSRなので、それをCDNでキャッシュさせる方がシンプルで良かったかも・・・ まとめ 43

Slide 44

Slide 44 text

©MIXI ● ヘッドレスCMSでWPGraphQLは大変捗るので良い ● GraphQL使うならぜひFragment Collocationの恩恵を受けよ ● WordPressの有料テーマは完成度が玉石混交でリスクが高いので、カスタムテーマを使うか、完成度が ハンドリングできる方法(外部委託、OSS、etc…)の物を使おう ● グループレイアウトを使うとSPA(Single Page Application)のメリットかつデメリットでもあるリロードが走ら ない問題に対処できる ● Next.jsのキャッシュ機構は非常に複雑。よくドキュメントを読もう ● Next.jsはヘッダーも勝手によしなに操作するので、意図した挙動か確認しよう 総まとめ 44

Slide 45

Slide 45 text

©MIXI フロントエンド分離のためのインフラ 45

Slide 46

Slide 46 text

©MIXI ● 概要 ● フロントエンド分離未対応ページへのルーティング ● まとめ フロントエンド分離のためのインフラ: 目次 46

Slide 47

Slide 47 text

©MIXI 概要 47

Slide 48

Slide 48 text

©MIXI 概要 ● AWS上で構築されており、AWS CDKでコード管理されている ● Next.jsやWordPressはECS (Elastic Container Service) を使って動かしている ● 各ECSタスク内にはサーバー (Next.jsやWordPress) とnginxのコンテナが存在する 48

Slide 49

Slide 49 text

©MIXI 概要 ● ECS Serviceの前段にCloudFrontとそちらへリクエストを飛ばすためのproxy ECS Serviceが あり、ページをキャッシュしている 49

Slide 50

Slide 50 text

©MIXI 概要 ● 管理画面用のWordPress ECS Serviceもあり、こちらはフロントエンド分離せずに運用している ● 管理画面用のWordPressは複数立ち上げると管理画面にログインできなくなるため、 1台固定で運用 50

Slide 51

Slide 51 text

©MIXI 概要 ● リクエストはALBを使って以下のようにルーティングしている ○ ページ: Next.js ECS Service ○ GraphQL API: WordPress ECS Service 51

Slide 52

Slide 52 text

©MIXI 概要 ● Next.js ECS Serviceへのルーティングの際はLORアルゴリズムを用いることで、各ECSタスクに負荷が 均等に分散されるようにしている ● LOR (Least Outstanding Requests) アルゴリズム: 未処理のリクエスト数が最も少ないターゲット (ここではECSタスク) にリクエストを送信するアルゴリズム 52

Slide 53

Slide 53 text

©MIXI フロントエンド分離未対応ページへのルーティング 53

Slide 54

Slide 54 text

©MIXI フロントエンド分離未対応ページへのルーティング ● フロントエンド分離未対応のページは、Next.js ECS Service内にあるnginxから ECSのサービスディスカバリーを使ってWordPress ECS Serviceに飛ばし、 従来通りWordPress ECS Serviceでレンダリングしている ● これにより、フロントエンド分離対応・未対応ページが混在した状態での運用を可能にしている 54

Slide 55

Slide 55 text

©MIXI フロントエンド分離未対応ページへのルーティング ● WordPress ECS Serviceをローリングアップデートした際に、WordPressコンテナが立ち上げ 途中にも関わらず、ECSタスクがサービスディスカバリーに登録されてしまう問題があった ● これを防ぐため、WordPressのECSタスク内にあるnginxコンテナからWordPressコンテナへの 疎通を確認するコンテナヘルスチェックを設定している 55

Slide 56

Slide 56 text

©MIXI フロントエンド分離未対応ページへのルーティング ● また、サービスディスカバリーを使用するにあたり、ECSタスクに手を加えている (次スライド以降で説明) 56

Slide 57

Slide 57 text

©MIXI フロントエンド分離未対応ページへのルーティング 57 ● 以下の図は構成図からNext.jsとWordPressのECS Serviceのみを切り出した図である

Slide 58

Slide 58 text

©MIXI フロントエンド分離未対応ページへのルーティング 58 ● サービスディスカバリーを使ったNext.js ECS ServiceからWordPress ECS Serviceへの アクセスは以下の手順で行われる 1. Route53でWordPressのECSタスクの名前解決を行う 2. Route53から取得したIPアドレスにリクエストを投げる

Slide 59

Slide 59 text

©MIXI フロントエンド分離未対応ページへのルーティング 59 ● Route53から取得したIPアドレスはNext.jsのECSタスク内にあるnginxにキャッシュされる ● キャッシュ期間はRoute53内のレコードのTTLに依存する

Slide 60

Slide 60 text

©MIXI フロントエンド分離未対応ページへのルーティング 60 ● WordPress ECS Service内でECSタスクのローリングアップデートが発生すると、 Route53に新ECSタスクのIPアドレスのレコードが登録される ● Route53で名前解決を行った場合は新ECSタスクにリクエストを投げる

Slide 61

Slide 61 text

©MIXI フロントエンド分離未対応ページへのルーティング 61 ● しかし、Next.jsのECSタスク内にあるnginxにキャッシュが残っている場合、 旧ECSタスクにリクエストを投げてしまう

Slide 62

Slide 62 text

©MIXI フロントエンド分離未対応ページへのルーティング 62 ● この状態を避けるため、WordPressのECSタスク終了前にRoute53のレコードのTTLと 同じ秒数のsleepを入れ、Next.jsのECSタスク内にあるnginxにキャッシュが残っている間は 旧ECSタスクへアクセスできるようにした

Slide 63

Slide 63 text

©MIXI ● Next.jsやWordPressはECSを使って動かしている ● ALBを使ってページのリクエストはNext.js ECS Service、GraphQL APIのリクエストはWordPress ECS Serviceに投げている ● Next.js ECS Serviceへのルーティング時はLORアルゴリズムを使って各ECSタスクの負荷を 均等に分散している ● フロントエンド分離未対応のページは、Next.js ECS ServiceからECSのサービスディスカバリーを使って WordPress ECS Serviceに飛ばし、従来通りWordPressでレンダリングしている ● サービスディスカバリーを使うにあたり、WordPress ECS Serviceにおいて、 コンテナヘルスチェックを入れたり、ECSタスクの終了を遅らせたりしている フロントエンド分離のためのインフラ: まとめ 63 この後、ASK THE SPEAKERにて質問を受け付けますので、是非お越しください!

Slide 64

Slide 64 text

©MIXI