asoview!のWebパフォーマンス改善の取り組みについて

こんにちは。アソビュー株式会社でフロントエンドのテックリードをしている井上です。 先週会社の同僚と行ったボルダリングでダメージを受けた手にむち打ち、今日も狩りに出るためコントローラーを握りしめる日々です。 今回Webアプリケーションの表示速度高速化について書きたいと思います。

最近は特に日経電子版やdev.toなどの先端事例が話題になったり、SEO観点での重要性の高まりやPWA、AMPなど高速化にも関連する新しい技術や手法などWebサイトのパフォーマンスは注目度が高いトピックだと思います。

弊社でもUXやSEOの重要な要素として表示速度については目標値を設けて取り組んで行こうとしています。 直近数ヶ月でもasoview!のサイトを対象にいくつか取り組みをしたのでその内容について軽く触れられればと思います。

取り組む前の状況

私は昨年の11月にジョインしたのですが、弊社のビジネスの中心となっている日本全国の体験アクティビティやチケットなどの情報を紹介するマーケットプレイスであるasoview!は10月にリニューアルのリリースが完了したばかりでした。

かなりの大規模リニューアルで1年以上の期間と何度も軌道修正があったこともあり非機能要件である画面表示のパフォーマンスに手が回っていませんでした。

アーキテクチャを簡単に説明すると、サーバーサイドはJava(Spring boot)で画面の切り替えはサーバーサイドのコントローラーのルーティングで実施している非SPAのサイトでViewについてはThymeleafとReactのレンダリングの混在でJS、CSSはWebpackとgulpを使い1画面に1JS、1CSSの読み込みをしています。非SPAのサイトとしてはよくある感じ(?)なのかもしれません。

そのタイミングでは体感でも何かもっさりしており、Pagespeedやlighthouse、Webpagetestの点数でも中々ひどい状況でした。 f:id:masino83:20180226155847p:plain 11月時点のPageSpeedの点数。赤点ですね。。

f:id:masino83:20180226184227p:plain lighthouseも赤点ですね。

改善しがいが有りますねー! というわけで改善の取り組みを開始しました。

調査

まず、何が問題なのかの調査から入ります。 ツールとしては下記をつかいました。

Pagespeed insight

  • Googleが定める表示速度のベストプラクティスをどのくらい実践できているかを点数で表し、やるべきことを示してくれる
  • あくまでベストプラクティスのチェックであり実際のファイルロードや表示のタイミングなどは基準にしていない点数。
  • 最近のアップデートでChrome User Experience Reportを組み入れてリアルユーザーモニタリングの秒数分布も表示するようになりましたね

ざっくりした状況はこれでつかめます。 画像が重い、JS、CSSなどがレンダリングブロックしてるなど。 フロントエンドレイヤで遅いサイトはほとんどこれが原因ですね。

lighthouse

  • Speed IndexやFirst Meaningful Paint、First Interactiveなど実際のユーザーの体感速度としての良し悪しをサイトにアクセスして表示するまでのブラウザの振る舞いを解析して数値化する
  • ChromeのdevtoolsのAuditタブに組み込まれている
  • Node CLIで動かすこともできる

表示完了までの各タイミングや秒数、実際の画面の見え方の推移(表示完了までを10等分したタイミングごとの画面のキャプチャ)まで出してくれるので、なんかもっさりする、、の体感が実際どういう内訳なのかを探ることができます。

Chrome Devtools

  • みんな大好きcommand+option+iで起動のDeveloper Tool。主にパフォーマンス・チューニングで使うのは下記
  • Networkタブでリソースのダウンロードのタイムラインを見る
  • PerformanceタブでLoading、Scripting、Rendering、Paintingなど画面表示で起こっている事象について、メモリヒープサイズ、CPU、Network、イベントなどの観点で解析してボトルネックを特定できる
  • 前述の通りAuditタブでlighthouse実行

普段の開発の時もデバッグなど使われることも多いと思いますが、パフォーマンスチューニング時も結局これを使う時間が一番長いですね。
特にSP画面の場合、AuditタブでNetworkの設定をFast 3Gにして結果を解析するのがおすすめです。体感としてのボトルネックや改善効果が見やすいです。(Slow 3Gだと遅すぎて解析するにはさすがに少しイライラします。)

これらの結果特定できた課題に対して優先順位付けて下記の対策を実施していきました。

対策(優先順位順)

1. 画像サイズが大きく、FirstViewに入っていない画像も同期ロードしている

なんだかんだでこれが一番インパクトが大きかった

  • 数メガバイトサイズの画像が複数存在し、それを同期的呼んでいた。
  • 結果、後続のJSのロードや実行も遅れていしまい、JSでレンダリングしているコンテンツがいつまでたっても表示されない、JSを使う操作もいっさいできない
  • それに対していわゆるabove the foldに含まれる情報を優先的に表示させるための対策を実施
  • 古くからある定番のやり方ですが、画像の遅延読み込み(lazy load) するように設定。
  • nginxの設定で画像のリサイズをしました。
  • 巨大な画像は圧縮かけて軽くしました。

2. 画面のJSファイルが重く、Reactで描画している部分の表示が遅い

ここは色々やったので詳しく紹介

bundleファイルの内訳調査
  • まずWebpack bundle analyzer を活用して何が重いのかを確認しました f:id:masino83:20180226102237p:plain (サイズ:gzip後209.72kb)一部のUIでjQueryを利用してるのですが、でかいですね。また、momentも必要無いlocaleのファイルが大量に含まれています。
CommonChunkPluginを利用して共通モジュールを切り出す
  • jQuerymomentreact-domについてはほぼ全画面でつかっているのでCommonChunkPluginを使ってWebpackのruntime処理とともにvendor.jsという名前で切り出しました。
  • 全画面でvendor.jsを読むのでキャッシュ効率も高く、一度どこかの画面開いてしまえばその後はそこのロードは高速です。
  • ちょうど先日リリースされたWebpack4.0ではこのプラグインの設定は無くなります。 splitChunksオプションの設定で適切なChunkファイルを作ってくれる模様。(これから検証します。)

RIP CommonsChunkPlugin.md · GitHub

IgnorePluginでmoment.jsのlocaleを除去する
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  • 除去した上で必要な箇所ではmoment/locale/jaをimportすれば良い
dynamic import
  • EcmaScriptのTC39プロポーザルにも含まれている仕様の先取り
  • Code Splitting
  • import()を使ってモジュールのlazy loadingを実現する
  • lazy loadingしたモジュールはchunkファイルとして非同期でロードされる
JSファイルの改善結果

f:id:masino83:20180226150850p:plain (サイズ:gzip後32.41kb)CommonChunkに含んだモジュール、dynamic import対象のモジュールが除外されている

209.72kb => 32.41kb 85%削減!

3. CSSの読み込みが遅く、ブロックによって描画開始が遅れている

  • webfontの読み込みが並列になっていたのでおかしいなと思ったら、@importでsassの中でURL直で呼ばれており、後続ダウンロードをブロッキングしていたため外出した。

改善結果

PageSpeed Insights(29(Poor)=> 82(Good)) f:id:masino83:20180226205819p:plain

lighthouse(39 => 61) f:id:masino83:20180226205635p:plain

このように改善できました。偏差値なら中堅校くらいなら受かる感じでしょうか?
また、CVRについてもこの期間で倍増!・・というほどではないものの、徐々に上がっているというデータが出ており表示速度改善が寄与している可能性があると思われます。

さらに何ができるか、取り組んでくること

計測について

  • 継続的に改善するのであれば、基準と指標(KPI)が必要
    • よりリアルユーザーの体感速度に近い指標としてGoogleの提唱するSpeed Indexを中心に取ると良さそう
  • 外乱の無い環境での継続的な計測が必要
    • WebpagetestやlighthouseなどのツールでSpeed Indexなどが取れる
    • headless chrome × lighthouse on Node.js で自前で取ることができる
  • 自前のモニタリングツールを作成しました f:id:masino83:20180226182221p:plain
    • 競合サイトとの比較も実施しています。(名前は伏せました!)
    • 設定したURLに対してlambda上でHeadless Chrome、lighthouseを定期実行(Cloud Watch event)してS3に結果を格納する
    • グラフ画面はReactのチャート系ライブラリでささっと作ったやつです(もうちょっと見やすく直します。そのうち。。)
    • アナリティクスなどで取得しているCVRや離脱、直帰などとの値と照らし合わせて表示速度との関連性なども求められるようにしたいと思います。

Mobile Speed Scorecard and Impact Calculator(Googleの新計測ツール)

ちょうど良いタイミングでGoogleが新しい計測ツールをリリースしましたね。

これは全Chrome(のうち情報収集を許可したユーザー)のリアルなデータ(Chrome User Experience Report)をドメイン単位に集計した結果のようなので、ドメインに紐づくあらゆるページ(known by Google's web crawlers)の平均値になってるようです。

主要導線以外も含まれてしまうのでこれを目標値として改善を進めるのは適切ではないですが、ドメイン単位でサイト全体が速いに越したことは無いので定期的にウォッチしていくと良さそうです。

売上インパクトも出せるCalculatorもあるので表示速度の取り組みについて提案ベースで進めるフェーズだと活用できそうです。 f:id:masino83:20180227141543p:plain

さらに何ができるか

まだ課題は山積みですし、ブラウザの進化によって色々と改善できそうなポイントは多いので優先順位を付けて色々試していこうと思います。 現時点で考えているのは下記です。

  • サーバーの初期レスポンスが1秒以上かかるページがまだあるのでabove the foldに影響無いコンテンツはapiを切り出して非同期化して、初期のHTMLのレスポンスを早くする。(そもそも初期HTMLが返って来ないとJSやCSSのダウンロードや描画をいくら早くしても意味がない)
  • CSSについてUIのReact Component整理と合わせてCSS in JSなどを活用してJSと融合することで、dynamic importなどの恩恵に預かられるように。
  • Reactコンポーネントのサーバーサイドレンダリングによって初期読み込みのタイムラグを無くす
  • PRPLパターンに従ってService Workerを活用してリソースの事前キャッシュ、遅延読み込みをより促進していきたい

まとめ

表示速度はサービスのエンドユーザーにとっての印象や売上に直結するものの、非機能要件ということもあり、エンジニアが自発的に取り組まないとどんどん劣化していくものだと思います。
PWAなどブラウザやフレームワークの進化など、技術の進歩に伴ってできること、やるべきことが刻一刻と増えていくため、最新の情報をウォッチいち早くキャッチし、プロダクトに適用してゲストがasoview!を通して良いあそびに出会うためのサイト体験をより良いものにしていきたいと思います。

アソビューでは一緒に働いていただけるエンジニアを絶賛募集中です。(特にフロントエンドに強い方を探しています)ご興味ある方はぜひ下記からエントリーしてください!
www.wantedly.com

www.wantedly.com