こんにちは。エンジニアの北島です。普段は LIFULL HOME'S の売却査定領域でエンジニアリングを担当しています。
今回は既存アプリケーションのパフォーマンス改善に、フロントエンドの観点から取り組んだ話をします。
経緯
弊社のサービスでプライスマップという AI 査定による不動産価格を地図上で一気に見られるサービスがあります。
このサービスは 2015 年にローンチされ、当時は最新の技術を利用していたものの現在は老朽化が進んでいました。
私どもの部署では昨年このサービスの運用主幹となり、昨年度はバックエンドを弊社で運用しているコンテナオーケストレーション基盤keelに載せ替え、インフラ改善を中心に取り組んで参りました。
今回は既存のアプリケーションのフロントエンドパフォーマンス改善を、Lighthouse を用いて行った話をしていこうと思います。
現状確認
このプライスマップにおいて、Google が検索エンジンにおけるサービスの評価指標としても採用している CoreWebVitals の各評価指標にも改善の余地がありました。
Google が無料で提供している Web サイトを分析・診断するための、Chrome 拡張機能Lighthouseを使用し手元で診断してみたところ、 特にモバイルで低い評価でした。
引継ぎの時にサーバサイドのパフォーマンスチューニングを行っていたこともあり、サーバサイドレスポンスタイムに大きな問題はなく、フロントエンドの方に改善の余地がありました。
フロントエンドパフォーマンス改善に取り組んだ経験がなかったのですが、SEO 観点での指摘ということで事業的なインパクトも期待できるので、今回着手することに決めました。
具体的な対応内容
対応事項の整理
Lighthouse のスコアを確認し、どれくらいの水準なのかを把握するために自社のほかのサービスと比べましたが、それらと比べても低い水準であることを確認しました。
経験の少ない自分としては情報は多い方がありがたく、自社のほかのサービスのスコアの方が上ということは自分にないノウハウが社内にあるはずだと考え、社内のエンジニアに help を出し情報を集めました。
その甲斐もあって、Lighthouse に直接指摘されている事項以外にも、パフォーマンス改善点を把握できました。
実際に対応した内容は以下の通りです。
- text compression
- JavaScript の遅延読み込み
- css の@import 廃止
- 画像の Webp 化
text compression
これが改善貢献度としては一番大きいものとなりました。
サーバからクライアントに静的ファイルを送信する際に圧縮した方が良いというものです。
静的なファイルなのできればアプリケーションから返すのではなく Contents Delivery Network を用いて実現したかったのですが。今回はアプリケーションから返す時に使用されるミドルウェアで gzip するという対応になりました。
一度用意すればあとはすべてのコンテンツが圧縮されて返されるので、それほど大がかりな対応なく効率的にパフォーマンスを改善できた点が良かったと思います。
JavaScript の遅延読み込み
これは一番時間がかかった作業になります。
対応内容としては JavaScript の読み込みを遅延するというものです。
async/defer 属性を付与することによって対応すればよいのですが、遅延されていない既存のページをリグレッションなく遅延させるのは、一筋縄ではいきませんでした。
既存のページは html 読み込み時にインラインスクリプトでJavaScript も読み込まれているため、単純にJavaScriptファイルを遅延読み込みしてしまうと動作しないコードでした。
下記は該当のインラインスクリプトの一例です。
<script> namespace("PriceMap.constants.MAP_CONFIG").zoom = 16; namespace("PriceMap.constants.MAP_CONFIG").center = { lat: 35.4477777777778, lng: 139.6425, }; </script>
lat と lng に実数を指定していますが、これらはテンプレートエンジンでは変数が指定されている箇所です。
テンプレートエンジンで変換する処理ですので、遅延読み込みすれば解決するような状態ではありませんでした。
まず最初に単に遅延読み込みを試しますが、js エラーが出ました。undefined のエラーだったのですが、まさに読み込み順の関係です。
テンプレートエンジン内に JavaScript が実装されていたので、こちらに関しても他ファイルに切り出したうえで遅延読み込みさせる必要がありました。
またテンプレートエンジン内に実装されていたコードはサーバサイドでの処理結果に応じて動的に JavaScript を作成する記述でした。
そのため、単にファイルとして切り出すのも難しい状況でした。
最終的には、3 段階で変更しました。
- 動的に変更になる、JavaScript 連携したいデータを data 属性で HTML に定義
- インラインスクリプトを撤廃し、js ファイルに移築する
- JavaScript を遅延読み込み
これらの変更はデータの渡り方や JavaScript の実行順が変更となる対応でしたので、テスト観点が多い作業となりました。
フロントエンドパフォーマンス改善の中で一番作業量が多かったかなと思います。
しかし作業の過程で既存の処理に関する理解が深まったので、ほかのリファクタリングも同時に行えたので、結果的には良い改善が行えたかなと考えています。
css の@import 廃止
これは社内の有識者からアドバイスいただいた項目で、css における@import が低速なので避けましょうということでした。
実際にこの記述はリポジトリ内には存在せず、アセットパイプラインを通して作成される記述のようでした。
結局bootswatch-railsという gem が生成するものだと判明したのですが、ここまで見つけるのにかなり苦労しました。
生成される@import の行をソースコードに含む GitHub リポジトリのうち、プライスマップが使っているかもしれない gem をあたって見つけました。
原因が見つかった後はこの gem がどこでなぜ使われているのかを調査し、代替手段を模索していきました。
最終的にはこの gem を使用する必要はないということが分かり、使用しないように変更したうえでリグレッションテストを行うことで、無事脱却できました。
画像の Webp 化
静止画の配信方法に関しての指摘を、Webp による配信に変更することで解消しました。
サーバサイドで配信する前に png や JPEG などの画像を Webp に変換して配信するしくみを導入する方法もありますが、今回は直接元の画像を Webp に変更する方法を採りました。
変更にはffmpegを使用し、リポジトリ内の特定の拡張子のファイルに対して一括で変換するスクリプトを使用しました。
#! /bin/sh array=`find src/app/assets/images/ -name *.png` for item in $array; do echo "ffmpeg -i ${item} ${item%.*}.webp" ffmpeg -i ${item} ${item%.*}.webp echo "rm ${item}" rm ${item} done
この方法だとファイルの拡張子が変わるので、参照するコードに関しても置換する必要があります。
これは sed コマンドで一括置換を行い、コミットの際に 1 つ 1 つ差分を確認して進めていきました。
成果
最終的なパフォーマンス改善後のスコアはこのようになりました。
少し分かりづらいですが、赤字の重要度の高い指摘について処理が短くなっており、改善していることがわかります。
一方指摘すべてを解消したわけではないので、改善の余地はまだまだありそうです。
良かったこと
指摘項目の解消を目指してリファクタリングするので実装方針を立てやすく、効果も見えやすいという点が非常に良かったです。
また指摘項目の解消を通してフロントエンドパフォーマンスの知識が身に着くので、自身の成長にもつながりました。
たいへんだったこと
既存の正常に動作しているシステムのリファクタリングですので、当たり前ですが既存のシステムの理解が重要でした。
中途半端な対応ですと既存システムがまったく動かなかったり、動くように対応しても細かい部分でリグレッションが起きたりなど、試行錯誤の連続でした。
その過程で既存のあまり詳細に意識していなかったところの構造理解が進んだり、細かい部分のサイト仕様を把握できたので学びの方が大きいのですが、それはそれとして作業としてはたいへんでした。
感想
lighthouse のパフォーマンス改善は、事業的な改善が見込め、エンジニアの成長にもつながると良いこと尽くめの印象で、ぜひ多くのエンジニアに経験してほしい体験だなと感じました。
フロントエンドエンジニアでなくとも、指摘の内容自体は広く一般的に分かりやすく説明されているものばかりですので、着手しやすいのではと思います。
まとめ
今回はフロントエンドのパフォーマンス改善に関して行ったことや、良かったこと、たいへんだったことを共有させていただきました。
もともと影響の多い text compression の指摘のみを解消すれば良い依頼だったのですが、指摘事項を確認するとほかにも改善できそうな点がいくつか見つかりました。
工数はかかりますが、いっそほかの指摘事項も対応できないか調査と対応をしても良いですか?と上長に確認を取ったところ、快く OK してもらえたのでこの作業が行えました。
対応しているときは仕様の決まっていないリファクタリングを行っている状態で、業務というよりは研究といった感触で楽しく進めることができました。
最終的にサイトのパフォーマンス評価も大幅に改善され企画サイドにも感謝されましたし、着手できてよかったなと考えています。
LIFULL ではこのようにともに成長していける仲間を募集しています。興味がわいた方はぜひともこちらのページもご覧ください。