はじめに

こんにちは、AmebaマンガでWebフロントエンジニアをしています、小林と岸です。

本記事ではAmebaマンガフロントエンドチームが取り組んだWebパフォーマンス改善の取り組みと成果を紹介します。

Amebaマンガのフロントエンドチーム紹介

Amebaマンガはマンガをスマホ完結で購入・閲覧できる、電子コミック配信サービスです。23時間待てば無料で読める無料連載、編集部による特集などのコンテンツで、新たなマンガとの出会いを支援しています。

Amebaマンガのフロントエンドチームは5名の体制です。うち半数がプロダクトの運用に関わる新機能の開発や改修をメインに、もう半数が今回お話しするパフォーマンスを中心としたWEBの品質改善をメインに日々開発をおこなっております。

なお、建てつけ上「パフォーマンス」と「プロダクト」の開発に二分となっておりますが、これは専任ではありません。

プロダクト運用には大きな機能追加や急を要する対応の発生はつきものであり、一方でパフォーマンス改善は開発に携わる一人一人の認識が必要だと考えております。

そこでフロントエンドチームでは専任の形はとらず、パフォーマンス改善のチーム内勉強会を必要に応じて開催したり、Design Docのレビューを通したプロダクト施策の開発共有を適宜実施することで、技術共有と相互のフォローを柔軟に行えるチーム体制を敷いております。

取り組んだこと

  1. 指標を決める
  2. 計測環境を整備する
  3. 技術ナレッジ
  4. コンポーネントの改善
  5. ページのコンテンツ構成を改善
  6. ブラウザバック時のRe-Rendering防止対応

指標を決める

お客様に一冊でも多くの新しいマンガに出会っていただくため、パフォーマンスの改善は長年の課題でした。

2020年末、事業責任者とランチをした時に「連続的な消費体験を提供したい」という話になり、具体的には以下のような問題点があがりました。

  • 初回ロードやページ遷移時にガタガタとコンテンツが読み込まれる
  • ブラウザバックでスクロール位置が変わってしまうことで迷子になってしまう

ここは自分でサイトを使っている際もストレスを感じている点だったため、本腰を入れて改善していくことに決めました。

上述の課題を解決するに当たって、まず取り組んだのは指標を決めることでした。

ページ読み込み時にガタついてしまう挙動、これはCumulative Layout Shift(以下CLS)という指標で示されており、Googleの提唱するCore Web Vitals = ユーザーエクスペリエンスの質の最適化における重要事項(以下CWV)に含まれています。

直近の目標としては、このCLSの改善なのですが最終的には全ての指標をクリアしてより良い体験を提供するのが目的なので、CWVを計測しつつCLSを重点的に改善していくことにしました。

Core Web Vitalsの指標画像

Largest Contentful Paint は、ユーザーがページで最も有意義なコンテンツをどのくらい早く見ることができるかを表します。感覚的な読み込みスピードを測定し、ページ読み込みタイムラインにおいてページの主要コンテンツが読み込まれたと思われるタイミングを指します。

First Input Delay は、最初の入力までの遅延を表します。応答性を測定して、ユーザーが最初にページを操作しようとする場合に感じるエクスペリエンスを定量化します。

Cumulative Layout Shift は、ページがどのくらい安定しているように感じられるかを表します。視覚的な安定性を測定し、表示されるページ コンテンツにおける予期しないレイアウトのずれの量を定量化します。

*画像、テキストはGoogle Developersの記事「web Vitals の概要: サイトの健全性を示す重要指標」から引用

計測環境を整備する

改善すべき指標が決まったので、次は計測環境を整えました。

もともとWebパフォーマンスの計測ツールとしてSpeedCurveを使用していましたが、CWVを重点的にウォッチしていく構成になっていなかったためDashboardを整備してFCP、LCP、TBT、CLSを改善対象のページ別に計測できるようにしました。

改善前のCLS値グラフ

着手前のサイトトップページCLS値。基準値0.1を大きくオーバーしています。

技術ナレッジ(ドキュメント作り/定例計測会)

パフォーマンス改善は開発に携わる一人ひとりの認識とチームで継続して取り組むことが大事だと考えてます。

せっかく改善しても、別件の開発で同じ問題をぶり返して指標が悪化したら焼け石に水です。 また、一時はパフォーマンスの改善に熱い気持ちを燃やしても日頃から新機能などの開発に追われてると、「あれどうなった?」という事態になりがちです(自戒)

そこで、フロントエンドチームでは以下の取り組みをおこないました。

  1. ドキュメント整備と勉強会の実施
  2. 「SpeedCurveを垣間見る会」の定例運用

1.ドキュメント整備と勉強会の実施

目指す状態は「パフォーマンス指標とその計測についてメンバー全員が理解できてること」です。

Amebaマンガのフロントエンドチームはメンバーのスキルセットはまちまちで、自分も含め誰しもがパフォーマンス改善について明るい訳ではありませんでした。

FCP FID TBT TTFB LCP CLS… パフォーマンスの各指標は略表記が多いです。 その時は覚えても、慣れるまではついついググってしまう方も多いのではないでしょうか。 (自分だけでしょうか)

そもそもWEBパフォーマンス改善の敷居あげてるのって、この略し方だと思うのは自分だけでしょうか

今回パフォーマンス指標として採用しているのがCWVなので、Web Vitalsの話を中心にドキュメントを整備・勉強会を開催しました。

以下の画像はその内容の一部です。

Core Web Vitalsドキュメントの一部

CLS対策のドキュメント

Web Vitalsについてのドキュメントは基本的にweb.devの内容を抜粋してなぞっているだけなのですが、必要に応じて注釈や補足を加えることですぐに概ねのことが解るように心がけました。

こうした優しい内容のドキュメントを整備して知見の底上げをすることで、メンバー間での理解度レベルのばらつきを減らしていきます。 その結果、チーム全体として効率よくパフォーマンス改善に取り組めればと考えております。

また、こうしたドキュメントは非エンジニア職の方へ取り組みを理解してもらう際にも一役買います。

2.「SpeedCurveを垣間見る会」の定例運用

目指す状態は「パフォーマンスに対する意識の習慣化」です。

「SpeedCurveを垣間見る会」ではリリースで追加された機能や改善に対して、パフォーマンスの各指標に変化が現れてないかをフロントチームの皆で確認することを毎週実施してます。

ファシリテーターを固定せず持ち回りにすることで、各メンバーの一人ひとりにパフォーマンスに対する当事者意識をもってもらいます。

画像は議事録の抜粋したものです。

パフォーマンス計測会議事録の抜粋

このように各指標に現れた変化に一喜一憂しながら、みんなでSpeedCurveを垣間見ます。

見つかった課題に対して具体的なアクションがある場合、項目化したのちにチケット化して適宜対応を行います。

見つかった課題に対する具体的なアクション

また、大きめの改善がみられた際はフロントエンドチーム外へのアピールも抜け目なくおこないます。

改善成果の共有

継続的なパフォーマンス改善に向き合うにはフロントエンドチームに括らず、他職種の方々の理解と協力が不可欠です。 パフォーマンスに向き合う姿勢を対外的に知らせる事で、協力の相談や依頼を行いやすくなります。

あと、地道な対応が多いので良い結果が出た時は祝う雰囲気を出したいです。 (SpeedCurveもスコアが改善された際に祝ってくれるとテンションが上がるのですが。。)

※とても祝ってくれるサイトの例

パフォーマンスの改善は一朝一夕で成せるものではなく、継続しておこなう必要があります。 一方、やる必要があると解っていても、つい後手になってしまう事もあります。

こうした習慣化をチームで取り組むことで改善を積み重ねることができ、 やがては大きな成果へとつなげる事ができるのではないでしょうか。

コンポーネントの改善

SpeedCurveでは、計測ごとに画面のレンダリングキャプチャ画像を撮ってくれてどこがレイアウトシフトしているかわかるようになっているのですが、基本的には計測ブラウザのファーストビュー(AmebaマンガではiPhoneXにしています)が対象となります。

画面外の部分はGoogle Search Consoleの「ウェブに関する主な指標」の測定グラフから改善を測定するようにしました。

また、目標としてはSpeedCurve上の計測結果を改善することなのですが、ユーザーの体験を損なっているのはファーストビュー以下のコンテンツ群であると判断してそちらから着手することにしました。

書影コンポーネントの改善

書影とは、マンガの表紙画像のことを指します。

Amebaマンガのページの7割は、この書影画像が占めており、様々な出版社からそれぞれの縦横比率で画像が納品されます。

これらをページ内で表示するにあたり、スマートフォンユーザーが圧倒的に多いAmebaマンガにおいては横幅を固定していました。このため、画像読み込み時に画像の高さががわからずレイアウトシフトの要因になっていました。

ここを改善するために、以下のような対応を行いました。

  • デザイナと相談して画像の比率を定める
  • imageのonLoad前はプレースホルダーの要素を表示させて高さを保持し、onLoad後に差し替える
  • 定めた比率に対して不足している部分はプレースホルダーの色を残すようにする(imageMagickの機能を使用)

これで、比率の違う画像も一定のサイズで扱うことができるようになりました。

書影を含むListコンポーネントの改善

書影の改善をしたことで、画像のURLを取得してから画像表示までのレイアウトシフトを防ぐことはできるようになりましたが、画像のURLを取得するまで・・・つまりAPI通信が完了するまでの間のレイアウトシフトは発生しています。

そこで以下のようなプレースホルダー、いわゆるSkeletonコンポーネントを作成しました。

テキスト、書影、ボタンのように細かい粒度でコンポーネントを分けることで、様々なレイアウトのデザインでも柔軟にSkeletonの作成ができるように作っています。

対応後の実際のコンテンツ。

ページのコンテンツ構成を改善

ここまでの対応で、ファーストビュー以下のユーザー体験がかなり向上してきました。

ファーストビュー部分の改修に取り組みます。

ここで問題になったのが、「あったりなかったり」なコンテンツでした。

Amebaマンガでは、新刊の発売に合わせて特集を組んだりポイントアップキャンペーンなど毎日様々なコンテンツ運用が行われています。

露出は主にサイトトップのファーストビューで行われるため、この「あったりなかったり」なコンテンツがレイアウトシフトの要因になっていました。

ここは技術的な対応というよりも、レイアウトシフトにより損なわれるユーザー体験のデメリットをフロントエンドチームからの発信でプロジェクト全体で共有し、サイトトップのコンテンツ構成を変更することで対応しました。

一例としてSpeedCurveのレンダリングキャプチャをご覧ください。画面真ん中の緑の帯は「サイトからのお知らせリンク」用のスケルトンですが、API通信後に表示するデータが無かったためにレイアウトシフトを起こしてしまっています。

このファーストビューでのレイアウトシフトを防ぐために、コンテンツ運用のメンバーに、定常的にお知らせを表示してもらうよう協力してもらいました。

ブラウザバック時の再Fetch防止対応

「連続した体験」を実現するためにはCLS以外にも対応が必要でした。 それはブラウザバックをおこなった際の考慮です。

具体例な事例をあげます。Amebaマンガのトップページには「ある作品を読んでいる人にオススメの作品を推薦する」モジュールがあります。

このモジュールで用いてるAPIはリクエストをおこなう度に結果の内容が変わるものです。 (推薦元となる作品がランダムとなり、推薦される作品データを返す)

キャッシュ効率の低いAPIのfetchは、基本的にClientで非同期に行っています。 fetchは同セッションにおいてデータの取得済みか否かに関わらず、ページ遷移のタイミングで一律しておこなっていました。

これをリクエスト毎に違う結果を返すAPIでおこなうとどうなるか。 以下のGIF動画をご覧ください。

特定本からリンク先に遷移したのちブラウザバックで戻ると、推薦データが改めて取得されモジュールが別の内容に更新されます。 結果、遷移前に並んでいた他に推薦された作品の確認ができません。 これは「連続した体験」が損なわれてしまっている、と言えます。

連続した体験をつくるにはブラウザバックで戻った際でも遷移前と同じページの状態を復元する必要があります。

そこで、既に取得しているデータについては同セッションにおいては再度リクエストしないように制御を加えました。

export const fetchList = createAsync<
  { params: Params },
  List[]
>('FETCH_LIST', async ({ params }, dispatch, getState) => {
  // 取得対象となるデータの格納先を参照
  const { data } = getState();
  const dataList = data.list;
  // 既にデータがある場合はfetchしない
  const hasData = Object.keys(dataList).length > 0;
  if (hasData) {
    return null;
  }

  //...fetch処理に続く

このようなモジュールはTOPページを中心に散見されたので、ひとつひとつのActionを改修するといったCLS対応に負けず劣らずの地道な対応を行いました。 (まだ全てではありませんが、、、)

また、ブラウザバックの件からは逸れますが、ページングを伴うなど再利用生の高いデータは格納するデータを一意に特定できるようにStoreの構成を考慮することで取得済みのデータの有無を判定できるようにして、無駄なリクエストを行わないようにもしました。

// 作品IDとページ毎に一意に特定できるようにする
bookIdsPageMap: {
 `${titleId}/${page}`: [bookId, bookId, bookId, ...]
 `${titleId}/${page}`: [bookId, bookId, bookId, ...]
 `${titleId}/${page}`: [bookId, bookId, bookId, ...]
}

CLS改善結果

上述してきた対応を効果検証を細かく行いたかったため段階的にリリースしていきましたが、最終的に基準値である0.1のラインのクリアとGoogle Search Console上の計測値も大幅に改善し、体感的にも大幅にユーザー体験を向上させることができました。

改善の取り組みを行ってから現在まで、Amebaマンガにおけるユーザーのマンガ閲覧数が約3倍ほどに増えてきています。

先日行われたGoogle I/Oのトークセッション内でも、「Core Web Vitalsがもたらすビジネスインパクトの事例」として取り上げていただけました。

*画像、テキストはGoogle Developersの記事「Core Web Vitals によるビジネス インパクト」から引用

今後のパフォーマンスへの取り組みなど

Amebaマンガのフロントエンドチームではパフォーマンスに取り組む為の土壌づくりとして、ドキュメント整備と勉強による知見の底上げと定例会運用によるパフォーマンスに対する意識の習慣化にまず取り組みました。

その後「連続した体験」の実現のためにCLSの改善にメンバー総出で取り組み、先述した成果を出すことができました。

今後はCWVで挙げられてるCLS以外のFID(TBT)、LCPの指標にも本腰を入れて向き合います。 SEO、CrUX文脈での改善取り組み、ビルドチューニング、CSSのcontent-visibilityやReactのSuspenseListの導入検証など、まだまだやりたいことが沢山です。

そして先日、Why Did You Renderを入れて、地獄の蓋を開けてしまったばかりです。 ーーー我々の戦いはまだ始まったばかりだ!!

今回のお話がパフォーマンスに向き合う皆様の一助になりますと幸いです。