Reactのパフォーマンス改善でjsの実行速度を1.85倍早くしたお話

Wondernuts Inc.
9 min readJan 18, 2019

初めまして!LEAN BODY ( http://lp.lean-body.jp )のフロント全般〜APIまで開発を担当しているdaidaiです。

今回は、LEAN BODYの開発にあたって僕が実施したフロントエンドのパフォーマンス改善施策について紹介します。

LEAN BODYについて

LEAN BODY

LEAN BODY

月額1,480円(税抜)でフィットネスレッスン動画が見放題のサブスク制サービスです。Netflixのフィットネス版みたいな感じです。海外でも人気な「ピラティス」をはじめ、「ヨガ」「筋トレ」「ボクササイズ」など、幅広いジャンルのレッスン動画がパッケージになって収録されています。「FEVER腹筋」「#桃尻チャレンジ」など、体の部位に特化したレッスン動画も満載です。

速度の課題

ユーザからのお問い合わせで「サイトが重い」という声がちらほら見受けられました。当時は、実際初回ロードが遅かったり、HOMEにアクセスした時のロードも、体感で明らかに少し長いと感じるような状況でした。

当然、CVRやRR(リテンションレート)低下に大きく繋がるのでパフォーマンス改善を実施することに。

調査

ロードが遅くなる原因として考えられることはたくさんあります。「APIのレスポンスが遅い」「DBのI/Oが詰まってる」「レンダリング処理に問題がある」「フロントのアセットが大きい」「JavaScriptのバンドルが大きい」「処理が冗長」などなど。

キリがないので、まずはロードにかかる時間をChromeのDeveloper Toolで計測してみます。すると以下の結果になりました。

改善前のパフォーマンス

完全ローディングが終わるまで約6秒。そのうちScriptingが約4.5秒と、かなり微妙な結果です。ここで着目して欲しいのがScriptingという項目。ScriptingとはJavaScriptの実行時間のことで、JavaScriptファイルをダウンロードし、「実行を開始してから終了するまで」の時間のことです。

全体のウェイトの75%を占めている上に、4.5秒もかかっています。次はこの結果を改善するまでのプロセスを紹介します。

原因を調べる

まず調査の結果から入りますが、ロードが遅い原因は初回ロード時に読み込まれるバンドルファイルが大きいことが起因していました。

パフォーマンスの内訳を調べたところ、APIリクエストの時間も異常はなく、サムネイル画像もリサイズしているため特にアセットがボトルネックになっている訳ではなさそうでした。ということは、フロント側で何かが起きている可能性が高いと断定。

原因を調べる前に、まずはページ描画までのフローを分解してみます。LEAN BODYでは大きく分けて、次の流れでページが描画されていきます。

  1. 設定の読み込み
  2. APIクライアントなど初期化
  3. コンポーネント描画を担うClassを初期化
  4. react-routerのRootを描画

ここで着目したのが3番です。LEAN BODYでは、ページを描画する際にJSX.Elementを返すクラスを直接Routerに渡すのではなく、ページ描画用のClassを作り、そのクラスをRootComponentに渡しています。そしてRootの描画時はRootClassからページ描画用Classのメソッドを呼んで1ページづつ描画されるような流れになってます。

ページの描画時は、依存するロジックをページ描画時に初期化する、DIをベースに作っているため、親であるRootComponentからトップダウンでコンポーネントを作っています。

さらにLEAN BODYでは、ステート管理にMobxを使用していてMVVMアーキテクチャを採用しています。各ページにPageコンポーネントと、それに付随するViewModelがあるイメージです。

ここで、コードを地道にトレースしていって冗長な処理がないか調べたところ、Routerの描画時に全てのページのViewModelが読み込まれていることが判明()。

これはどういうことかというと、「1ページにしかアクセスしてないのにその他全ページのファイルが読み込まれている」のと一緒です。もっと簡単にいうと、必要のないコードが読み込まれている、ということですね。

CodeSplittingを導入して遅延描画する

Code Splittingとは名前の通り「コードを分割すること」です。SPAに精通している方ならご存知だと思います。先述した通り、必要のないタイミングで余計なコードが読まれている状況だったので、Code Splittingをして必要なタイミングでのみコードを読み込むようにしなければなりません。

Reactでは、Dynamic Import という機能を使ってCode Splittingを実現できます。とりあえず楽にできそうだったので react-loadable を使ってみることに。

react-loadableについて調べてみたところ、LINE社開発チームの記事 で「ページ遷移を即時に実行できなくなった」と指摘されていました。一応軽く試してみたところほぼ違和感なく動くことを確認。スクラッチでやるよりreact-loadableでやっていく方が早く楽にshippingできそうとなったので本格導入を進めることに。

結果

本番にデプロイし、再度ロード時間を計測した結果次のようになりました。改善前の結果も載せています。

改善前のパフォーマンス
改善後のパフォーマンス

総ロード速度も、一番改善したかったScriptingの速度も改善しています。パフォーマンス改善前は4560[ms]だったScriptingが、改善後には2685[ms]までスピードアップしました。4560 / 2464 ≒ 1.8[倍]早くなっています。

しかし、依然として2.6秒もかかっているので次はUserTimingAPIでコンポーネントのパフォーマンスを詳細に計測しつつ、Reconciliation最適化などを行いミクロな改善を行なっていく必要がありそうです。

まとめ

計測〜実装/デプロイまで、他のタスクと並行しながら2日程度で完了しましたが、思ったより少ない工数に対してインパクトの大きい改善結果が得られたのでよかったです。

ベンチャーは慢性的にリソースが不足しがちで、パフォーマンス改善も新機能追加もリソースとの兼ね合いみたいなところがあると思います。ネックをどれだけ許容するか、というのは結構大事だと思っていて、何か新しい機能を改善しようとなった時に、理想的な改善が毎回できれば良いですが、必ずしもそうはいかないもの。そこでスピーディに、「これはこれくらい許容しよう」と意思決定するには常にプロダクトの技術的課題をくまなくウォッチしておく必要があるなと思いました。

エンジニア募集

株式会社ワンダーナッツではエンジニアを募集中です。

技術スタック

フロント: React / TypeScript / RxJs /Mobx / AtomicDesign
API: Go / Echo
インフラ: AWS EB / Docker / RDS Aurora(MySQL) / SES / Lambda / Cloud Watch / Cloud Functions / BigQuery
ツール: CircleCI / Zenhub / Tableau
働き方: フレックス制
休日: 土日、祝日
福利厚生: 書籍代全額支給、交通費支給、希望のディスプレイを支給

開発フロー

1日目: 全体会議。スプリント計画や課題の議論
2日目: 要件定義、基本設計、開発
3日目: 開発
4日目: 開発
5日目: 開発、レトロスペクティブ(振り返り)

開発のみならずプロダクトの意思決定にも加担しながらゴリゴリ開発を進めていける環境です。与えられたタスクを正確にこなすのはもちろん、自分で仕事を作っていくタイプの人は弊チームにとてもマッチしていると思います。また、必要に応じて新しい技術を積極的に取り入れる風習が結構あり、最新の技術スタックでコードを書いていきたいという人にもかなりおすすめできるかと。

またReact、Goなどに精通していればすぐに第一線でコミットできる環境です。言語の経験がなくとも、他言語である程度の経験があればキャッチアップしたのちに活躍できる環境です。共にハイレベルで愛されるプロダクトを作り上げていきましょう。話だけ聞いてみたい!という方も大歓迎です、社長または僕のDMまでお気軽にご連絡ください。

https://twitter.com/hinodeya_pon

--

--