SPAリリース後の問題とその対策

森山智哉氏:それでは発表させていただきます。今回は「SPAリリース後の問題とその対策」についてお話しさせていただこうと思います。よろしくお願いします。

(会場拍手)

まず最初に自己紹介をさせていただきます。今回会場を提供させていただきました、ITプロパートナーズという会社でエンジニアをやっております。

「もりー@webエンジニア」というアカウントでTwitterもやっているので、よかったらみなさんフォローしてください。

まず、今日話すことの概要です。「プロダクトのリリース後に出てきたSPAならではの課題や問題をどのように解決していったか?」をお話しさせていただければと思っております。

まず最初に、ちょっとサービスの紹介をさせていただきます。私が今開発している「Graspy」というサービスなんですけれども、コンセプトは、新しい学び、新しい企業、新しい人との出会いを提供して、みなさんが新しいキャリアを積むときの助けになるようなサービスを開発しています。

具体的にどんなことができるサービスなのかというと、無料でオンラインの学習を受けられたりとか、Graspyに登録いただいている企業さんの求人や企業の情報を見られたり、定期的に開催される各企業の有名なエンジニアや経営者の方の勉強会に参加したりといったことができます。

最近ついに登録者が1万人を突破いたしまして、日経にも掲載されました。また、今月、UIが学べるカリキュラムをリリースしました。エンジニア向けのUIの基礎と、iOS・Android向けのUIが無料で学べるコンテンツがあるので、興味ある方はぜひ登録してください。

Graspyの技術構成なんですが、フロント側がVue、Nuxtを使ってSSRをしてます。サーバサイドはLaravelを使って、こちらは完全にAPIサーバとして使用しております。なので、Nuxtでフロントエンドのレンダリングをして、APIでデータを取ってくるって感じですね。

弊社はLaravelでの開発実績はあったんですけれども、Vueを使用してSPAのプロダクトを開発するのは今回が初めての試みでした。やっとの思いでリリースはできたんですが、リリース後にSPAならではのさまざまな課題にぶち当たってきて、そのなかでなんとか課題を解消してきたので、今回はその中でも2つほどみなさんにご紹介できればと思います。

画面の表示が遅い

では、さっそく本題に入りたいと思います。

課題その1、画面の表示が遅い。「SPAなんだから速いんじゃないの?」って私も昔はそう思ってたんですけれども、もちろんそんなことはなくて。しっかり設計しないと、まったく速くなく、普通にLaravelでレンダリングしたときのほうが速いという状況になってしまいます。

この課題に対して比較的簡単にできる2つの対策を、今回ご紹介させていただこうと思います。

1つ目、データを取得するタイミングを変更する。

何から何に変更したかというと、fetchというところから、createdというブラウザでも実行される部分に持ってきました。

今回、NuxtでSSRをしているので、初回のページレンダリングはNuxtで実行されて、その後のページ遷移はSPAとなって、完全にブラウザ側での実行になります。

Nuxtの場合、通常のVueのライフサイクルにプラスして、1個前にfetchという処理があるんですね。基本的にNuxtを使ってページに表示するデータを取ってくるところはこのfetchとかasyncDataってところで行います。今回、そのfetchで書いていた処理の一部をcreatedのほうに移行しました。

どの部分を移行したかというと、PCとかってだいたいこんなUIってみなさん見たことあると思うのですが、こんな感じで、ヘッダーと、メインのコンテンツを表示する部分と、あとサイドメニュー。多いときでは合計4つの別々のデータを表示しなければいけません。

今回は青くなっている部分に関して、APIをコールする部分をブラウザ側でレンダリングされてから行うようにしました。

初回ロード時にサーバ側ではなくて必ずブラウザ側で呼ばれるので、表示は一瞬遅れてしまうのですが、画面自体の最初のレンダリングは速くなりました。

2つ目、更新頻度が高くないデータは、Vueのビルド時に事前にAPIを叩いて取得する。

どういうことかというと、実際にデプロイするときはVueからJavaScriptにビルドする必要があります。そのビルドしたものをサーバに上げてデプロイ完了になるんですが、ビルドするときに更新頻度が低いコンテンツのデータを、ビルド中にAPIを叩いて、事前にJSONとして保持しちゃいます。

そのビルドしたJSとさっき保持したJSONごと、そのままサーバに上げてしまいます。そうすることで、ユーザーがページを表示するたびにAPIを叩いてデータを取得してくる処理がまるっと消えて、その分表示が速くなります。

デメリットとしては、ビルドのときに時間が少し増えてしまうのと、もし事前に取得してくるコンテンツが更新されたとき、もう一度デプロイをしないと最新の状態が反映されないので、そこはコンテンツの更新頻度や普段のリリースの頻度を見て、どのコンテンツで行うかは判断する必要があります。

実際のコードはこんな感じです。Nuxtのbuildのbeforeでhookして、普通にAPIを叩いて、JSONのファイルとして「static」というディレクトリの配下に置いてしまう感じで構成しています。

この2つの対策によって、ページによっては表示速度が半分以下に、爆速になりました。

特定ページのAPI数が増加

課題その2、特定ページのAPIの数が増加してしまった。

機能追加や画面の複雑化、あとは初期段階の設計ミスなど、いろいろな要因で、一部のページのデータ表示用のAPIの数が増えてしまいました。多いページでは1ページに同時に7APIを叩く必要があるとかそんな状態になってしまって、このままだと特定ページにアクセスが集中したときにWebサーバの負荷が高くなってしまいます。

対策としては、一から設計をし直して、適切な粒度でAPIを切り分けて使用するのが一番いい方法なんですけれども、新機能の開発と同時で進めていくのはちょっと厳しかったので、APIをまとめる共通の仕組みを作ってしまいました。

どんな感じかといいますと、Vue側でPromise.allとかでAPIを同時にコールするときがあると思います。その場合にフロント側で複数のAPIを1本にまとめてサーバにリクエストを投げます。Laravel側では、その1本にまとめたAPIを処理するようなエンドポイントを用意しておいて、そこで順番に処理をしていくという感じになります。

まずリクエストを投げるときなんですが、まずフロント側でリクエストを投げるときに、axiosのラッパーみたいなものを私たちは用意しています。、Promise.allなので複数のAPIを同時にコールするときは数ms、実際に設定しているのは20msなんですが、APIを投げるのをちょっと待って、その間にCallされたGETのリクエストの、エンドポイントとクエリパラメータとかをObjectに追加していきます。

数ms待ってObjectに追加が終わったら、そのAPIの情報をまとめたObjectをまとめて処理する用のエンドポイントを用意しているので、そこに対してPOSTします。

Laravel側では、まとめて処理する用のエンドポイントでURLとかクエリパラメータなどのデータを受け取って、Controllerにそのまま渡して、APIのデータからリクエストを新しく生成して、本来のデータ処理に投げてしまいます。

開発のポイント

このLaravel側のソースコードを実際にお見せしたいと思います。こんな感じで、受け取ったURLのクエリパラメータとかデータを配列にしてループで回しています。

ポイントとしては、このループの中で、リクエストからアローでduplicateというのが入っていると思うんですけれども、ここで新しくリクエストのObjectを生成して、そのあとに、array_mapで実行する関数を変数に詰めて、そこで全部に対して実行するみたいな、ちょっと凝ったことをやっています。

まとめ。実際に開発してみて、SPAだとAPIとかフロントのライフサイクルをしっかりと意識しないと簡単に破綻してしまいます。気づいたときにはけっこう修正するのが大変になってしまいます。でもSPAって実際に開発しているとけっこう楽しいです。みなさんもぜひSPAでプロダクトの開発を行ってほしいなと思います!

ちなみにITプロパートナーズでは、LaravelとVueのSPAのサービス開発したいエンジニアを絶賛募集しております。 ご清聴ありがとうございました。

(会場拍手)