「改善して終わりにしない」フロントエンドのパフォーマンスモニタリング

ogp

この記事はenechain Advent Calendar 2025の18日目の記事です。

はじめに

enechainでソフトウェアエンジニアをしている @nakker1218 です。

私たちのチームでは、電力取引仲介を行う同僚(ブローカー)たちが使う、注文管理システム「eNgine」を開発しています。

ブローカーは、顧客から注文を預かり、eNgineに入力してチームで共有し、画面を見ながら顧客と交渉して成約を目指します。忙しい時間帯には複数の顧客から同時に注文が入るため、1分1秒を争う状況で素早く入力する必要があります。

eNgineは、そうした大量の注文をリアルタイムに表示・同期するためのツールです。

eNgineの画面イメージ

運用開始してから半年が経った頃、ブローカーから「eNgine結構もっさりしているんだよね」というフィードバックが上がってきました。

ブローカーは顧客から口頭で一気に10件以上注文を預かることがあり、スムーズに注文が登録できないと機会損失を生み、売上の悪化に直結する重要な問題でした。

この問題に対して、私たちのチームではパフォーマンスの計測・改善をしたうえで、Datadogを使いモニタリングできる体制を構築しました。

この記事では、フロントエンドのパフォーマンス問題を、単発の改善で終わらせず、継続的に品質を維持する仕組みについてご紹介します。

計測

もっさり感の正体を特定する

フィードバックにあった「もっさりしている」がどこで発生しているか、何を指しているかを明確にする必要がありました。

そこで、Datadog RUMのSession Replayで当時の操作を確認しつつ、実際にブローカーの業務を後ろで見ながらeNgineの使われ方を観察・ヒアリングしました。

すると、注文しようとしたときに画面の更新が遅く、次の操作ができなくなっているのが一番の課題だとわかりました。また、朝は比較的問題なく動いているのに、夕方にかけて遅くなる傾向があることも見えてきました。

もっさり感を示す指標

この課題を数値化し、改善を定量的に表す指標として、Core Web VitalsInteraction to Next Paint (INP) を使うことにしました。

INPはユーザーがページ上で行った操作に対して、ブラウザが視覚的な更新を行うまでのレイテンシを測定し評価する指標です。

今回の課題は「操作してから画面が更新されるまでが遅い」というものだったので、まさに操作後の応答を測るINPが適切だと判断しました。

Core Web Vitals 出典: https://web.dev/articles/vitals?hl=ja#core-web-vitals

INPの計測

INPの計測には以下の2つのツールを使いました。

Datadog RUMで全体の傾向を把握し、Chrome DevToolsで実際の操作のレイテンシを詳しく調べるという使い分けをしました。

Datadog Real User Monitoring (RUM)

Datadog RUMでは、ページごとにCore Web Vitalsの各メトリクスを実際のユーザー環境から収集できます。

Datadog RUMでみたCore Web Vitalsのメトリクス

INPのメトリクスを見たところ、観察で感じていた「夕方にかけて遅くなる」傾向が数値としても確認できました。

Datadog RUMでみたINP

Chrome DevToolsのパフォーマンスパネル

Chrome DevToolsのパフォーマンスパネルでは、操作単位でレイテンシを確認できます。

夕方に本番環境で注文モーダルを開く操作を計測したところ、約800msかかっていました。Core Web Vitalsの定義でもINPが500msを超えてくる場合、ページの応答性が低いとされ、非常に体験が悪くなっていたことがわかります。

Chrome DevToolsのパフォーマンスパネル

改善

原因の仮説

計測結果から、チームで「なぜ夕方に遅くなるのか」を議論しました。

eNgineは1画面に大量の注文を表示する特性上、誰かが注文を追加・更新するとリアルタイムに全員の画面へ反映されます。

朝は注文が少ないから問題ないが、夕方になると注文が増えて描画するDOMの数も増える。そして更新頻度も上がりそのたびにレンダリングが走って、操作がブロックされているのではないかという仮説を立てました。

この仮説から、改善の方針を2つ定めました。

  1. 表示するDOM数を減らす
  2. 不要な再レンダリングを減らす

修正

表示するDOMの数を減らす

表示するDOMの数を減らす方法として、ページネーションと仮想スクロールの導入を検討しました。

比較した結果、ブローカーは複数の注文を並べて比較しながら見たいというニーズがあり、1分1秒を争う業務の中でページ遷移のクリックをする時間も惜しいことから、ページネーションは見送り、仮想スクロールを導入しました。

仮想スクロールの実装には、TanStack Virtualを使いました。これにより、注文数が増えても画面に表示されている分のDOMしか描画されなくなりました。

不要な再レンダリングを減らす

仮想スクロールを導入しましたが、ブローカーは大きいモニターで全体を見渡しながら使うため、一度に表示されるDOMの数は依然として多く、改善には限界がありました。

そこでReact Developer ToolsのProfilerを使って、不要な再レンダリングをしているコンポーネントがないか調査したところ、予想通り親コンポーネントの更新時に、変化のない子コンポーネントまで再レンダリングされている箇所が見つかりました。

再レンダリング状況 (Before)

そのため、memo化と不要なpropsリレーを減らす対応を行ったところ、該当コンポーネントのレンダリング時間が120msから20msに改善しました。

再レンダリング状況 (After)

改善結果

仮想スクロールの導入と再レンダリングの削減により、注文モーダルを開く操作が800msから150ms程度まで改善しました。

改善結果

夕方にかけて悪化していたINPのスコアも安定し、ブローカーから「前より動作がサクサクしている」というフィードバックをもらえるようになりました。

改善後のINP

改善後のフィードバック

モニタリング

計測と改善によって現状のeNgineにおけるパフォーマンス問題は修正されました。しかし、改善して終わりではなく、継続的に品質を維持できる仕組みが必要です。ユーザーから「また遅くなった」と言われる前に、素早く気づける状態を作りたいと考えました。

そこで、重要な操作ごとに、INPを可視化するダッシュボードをDatadog上に作成しました。これにより、異常があれば私以外のメンバーも気づける状態になりました。

操作ごとのINPの計測

操作ごとのINPの計測には、Datadog RUMのユーザーインタラクションの追跡機能を使いました。

この機能では、クリックに続くアクティビティの検出をします。ページにアクティビティがなくなると、アクションは完了したとみなされ記録されます。

記録される要素は以下です。

メトリクス タイプ 説明
action.loading_time 数値(ns) アクションのロード時間。
action.long_task.count 数値 このアクションについて収集されたすべてのロングタスクの数。
action.resource.count 数値 このアクションについて収集されたすべてのリソースの数。
action.error.count 数値 このアクションについて収集されたすべてのエラーの数。

操作ごとのINPの計測にはaction.loading_timeというメトリクスを使います。loading_timeでは、アクションが発生してから画面のアクティビティ(リクエスト、JS・CSSの読み込み、DOMの更新)が終わるまでの時間を計測できるため今回のケースに最適です。 アクティビティの計測の詳細こちらをご覧ください。

ダッシュボードで操作を追跡するには、操作に名前をつける必要があります。Datadog RUMでは、クリック可能な要素にdata-dd-action-name属性をつけることで定義できます。

<Button data-dd-action-name="new-order-modal.open" />

名前をつけることで、action.loading_timeを操作ごとに集計できるようになります。これでダッシュボードで追跡する準備が整いました。

ダッシュボードの作成

操作ごとのaction.loading_timeを集計するダッシュボードを作成しました。設定手順は以下の通りです。

  1. Add WidgetsからTimeseriesを選択
  2. Graph your dataでRUMActionsを選択
  3. フィルターにアクション名を指定:@action.name:"new-order-modal.open"
  4. Markersに基準値を設定(200ms、500ms)

Widget追加

Core Web VitalsではINPについて200ms以下がGood、200ms-500msが改善が必要、500ms以上は応答性が低いと定義されています。この基準をマーカーで表現することで、異常があれば一目でわかるようにしました。

INPの基準値 出典: https://web.dev/articles/inp?hl=ja#good-score

重要な操作ごとに同様のWidgetを追加し、一覧で確認できるダッシュボードを作成しました。

Dashboard

ダッシュボードの運用

操作ごとのINPを可視化したことで、実際のユーザー環境でのパフォーマンスを継続的に確認できるようになりました。

当初はパフォーマンス課題の検知を行うために、閾値を超えたら自動でアラートを上げるモニターの設定もしていました。しかし、eNgineはリアルタイムに更新されるプロダクトのため、注文しようとしたタイミングで他のブローカーが注文を作成すると、その通信分だけアクションの時間が長く計測されてしまいます。そのため偽陽性が高くモニターは断念しました。

現在は、リリース後にダッシュボードを確認し、傾向として大きく変わっていないかをチェックしています。 これによってパフォーマンスの課題をユーザからアラートが上がる前に検知し、改善に取り組めるようになりました。

また、ダッシュボードはSession Replayにも紐づいているため、パフォーマンスが悪化した際に実際のユーザーの画面がどのように描画されていたかを動画で確認することもできます。

まとめ

この記事では、ユーザーからのフィードバックをもとにフロントエンドのパフォーマンスを計測・改善し、継続的にモニタリングする仕組みを作った事例を紹介しました。

今回の取り組みにより、パフォーマンスを改善しただけでなく、誰でも継続的にモニタリングできる体制が整いました。機能追加によってパフォーマンスが悪化していないかを確認できるため、安心してリリースできるようになりました。

同じようにフロントエンドのパフォーマンス改善に取り組んでいる方や、改善後のモニタリング方法を知りたい方の参考になれば幸いです。


enechainでは、事業拡大のために随時仲間を募集しています。興味がある方はぜひお声がけください!

tech.enechain.com

カジュアル面談フォームはこちら herp.careers