Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ABEMA における CriticalCSS の活用例

Co9xs
April 28, 2023

ABEMA における CriticalCSS の活用例

2023/04/28 Muddy Web #5

Co9xs

April 28, 2023
Tweet

More Decks by Co9xs

Other Decks in Programming

Transcript

  1. ABEMA における Critical CSS の活用例 目次 Critical CSS の仕組みのおさらい ABEMA

    における Critical CSS の実践例 Muddy な対応が必要だった例 現状の課題 まとめ
  2. Critical CSS の仕組みのおさらい ブラウザはページ表示の前に CSS を読み込む必要がある = CSS ファイルは rendering

    block resource である ページに必要な CSS だけを先に読み込むことで、初期表示をブロックするよ うな読み込み時間を減らすことができる = Web Vitals の LCP の改善などにつ ながる
  3. Critical CSS の仕組みのおさらい Critical CSS 適用前 ページに必要ない CSS の読み込みも待つ必要がある <head>

    <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>html served from backend server</title> <!-- 全てのページで必要な CSS を head で読み込む --> <link rel="stylesheet" href="/path/to/main.css" /> </head> <body> <!-- 実際に必要なのはこのコンポーネントに対する CSS だけ --> <div class="awesome-component">This is awesome component</div> </body>
  4. Critical CSS の仕組みのおさらい Critical CSS 適用後 ページにあるコンポーネントに必要な CSS だけ先に読み込む <head>

    <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>html served from backend server</title> <!-- 初期表示に必要な CSS だけを inline で読み込む --> <style> .awesome-component { color: red; padding: 8px; } </style> </head> <body> <div class="awesome-component">This is awesome component</div> <!-- 全ての CSS は body の最後 = ページの描画後に読み込む --> <link rel="stylesheet" href="/path/to/main.css" /> </body>
  5. ABEMA における Critical CSS の実践例 abema-web の全体設計イメージ バックエンドチームが管理している Go のマイクロサービス群

    Web チームが管理している Node.js の BFF 層 gRPC 経由で各マイクロサービスを呼び出し、クライアントアプリケーシ ョンが期待する形に変換する 変換したデータをもとに HTML を組み立てて serve する クライアントの React アプリケーションは HTML を受け取り hydration する
  6. ABEMA における Critical CSS の実践例 1. SSR サーバーの Docker イメージのビルド、コンテナの起動

    2. ヘッドレスブラウザで Critical CSS の抽出を行い JSON に保存 3. サーバーから HTML を serve する際は JSON ファイルから利用 これらのステップが各環境のデプロイワークフローで行われる
  7. ABEMA における Critical CSS の実践例 1. SSR サーバーの Docker イメージのビルド、コンテナの起動

    2. ヘッドレスブラウザで Critical CSS の抽出を行い JSON に保存 3. サーバーから HTML を serve する際は JSON ファイルから利用 ビルド時に CircleCI の workspace [※1] に保存していたコードを利用 通常の環境で使っている SSR サーバーの Dockerfile を一部上書きして利用する SSR 時に gRPC 経由で呼び出す各マイクロサービスの向き先を独自のモックサーバーにする ページとして出現する可能性のあるコンポーネントを全て表示させる必要があるので独自 のモックデータを使いたい(詳細は後述) ※1 https://circleci.com/docs/ja/workspaces/
  8. ABEMA における Critical CSS の実践例 1. SSR サーバーの Docker イメージのビルド、コンテナの起動

    2. ヘッドレスブラウザで Critical CSS の抽出を行い JSON に保存 3. サーバーから HTML を serve する際は JSON ファイルから利用 penthouse [※1] というライブラリを利用している 内部的には puppeteer を利用して chromium 経由でページにアクセス 全体の CSS ファイル main.css からページに表示されている HTML のクラス名を基に抽出 CSS の抽出結果が含まれる JSON も CircleCI の workspace に保存 ※1 https://github.com/pocketjoso/penthouse
  9. ABEMA における Critical CSS の実践例 PostCSS で必ず抽出対象に含めたい CSS ファイルを parse

    してセレクタの一覧を取得し、そ れを forceInclude オプションに指定 画面外のはずの footer などの要素に CSS が当たっていないことでなぜか CLS が悪化してい たらしい const penthouse = require("penthouse"); const criticalCss = await penthouse({ url: `http://0.0.0.0:3000${path}`, forceInclude: isMobile ? mobileForceIncludeSelectors : desktopForceIncludeSelectors, });
  10. ABEMA における Critical CSS の実践例 1. SSR サーバーの Docker イメージのビルド、コンテナの起動

    2. ヘッドレスブラウザで Critical CSS の抽出を行い JSON に保存 3. サーバーから HTML を serve する際は JSON ファイルから利用 // JSON から対象の CSS を文字列として取得 const criticalCss = shouldEnableCriticalCss && criticalCssName ? manifest[`${criticalCssName}-${isMobile ? "mobile" : "desktop"}`] ?? null : null; // backend から返す html の head に埋め込む <CssLoader manifest={manifest} isMobile={isMobile} criticalCss={criticalCss} position="head" />
  11. Muddy な対応が必要だった例 penthouse による抽出結果のデバッグ CI で実行される Critical CSS 抽出処理をローカルで実行 実際に撮影されるスクリーンショットを出力して、何が足りないのかを検証する

    結果、モックサーバーに対象の gRPC メソッドが追加されておらず、そもそも SSR に失敗 していることが判明 const penthouse = require("penthouse"); const criticalCss = await penthouse({ url: `http://0.0.0.0:3000${path}`, forceInclude: isMobile ? mobileForceIncludeSelectors : desktopForceIncludeSelectors, screenshots: { basePath: `./_screenshots/${name}-${platform}`, type: "jpeg", }, });
  12. Muddy な対応が必要だった例 モックデータの追加 同一ページで出現する可能性のあるコンポーネントが全て出るようなモックデータを作る パターン A の CSS ⊆ パターン

    B の CSS の場合はパターン B で CSS を抽出する必要がある 例: ABEMA Web 版におけるシリーズページ(パターン A 、パターン B )
  13. 現状の課題 マイクロサービス側への新規 gRPC メソッドの追加とモックサーバー側の更新が連動しておら ず、手動で追加する必要がある 結果、新規画面を一通り実装し dev で確認するまで Critical CSS

    の抽出に失敗しているこ とに気づかなかった 😇 proto ファイルから TypeScript の型定義を生成する仕組み自体は整備されているので、そ れを利用してモックデータの追加をやりやすくしたい 同一ページでも条件によって全く異なる CSS が適用される場合は、モックデータで事前抽出 するアプローチに限界がある 実際のページへのアクセスごとに動的生成するなどの対応が必要?🤔