Meetup」が開催されました。同社が運営する一休.comと一休レストランの開発に携わるエンジニアたちが、フロントエンド開発における知見を共有します。プレゼンテーション「一休.comレストランのスマートフォン検索ページがSPAになりました」に登壇したのは、株式会社一休システム本部、CTO室の稲尾遊氏。その解決までの道筋を語ります。システム本部CTO室スマートフォン検索ページがSPAになるまで稲尾遊氏:こんにちは。最後のセッションを発表させていただきます。レストランのスマートフォン検索ページがSPAになりました」と題して発表させていただきます。私は稲尾遊と申します。レストランのスマートフォン向けサイトのWebアプリ化をやっています。レストランを紹介させていただきます。2006年にリリースされた、高成長を続けるファインダイニングの予約サービスです。レストランを使っていただければと思います。今日はブログで書かせていただいた内容を踏襲するかたちでご説明させていただければと思います。JavaScriptについてはこのスライドには載せていません。期待してた方はごめんなさい。レストランのスマートフォン向けの検索ページになります。基本的にこの2つの画面があります。左側が検索結果で、右側は虫眼鏡をタップすると出てくる、検索条件を絞り込むためのパネル画面になります。この2つの画面はJavaScriptで動的に記述したSPA的な実装になっていました。この検索ページに対して社内的にも要求はあったんですけど。「もっと使いやすいUIにしたいよね」とか「単純にちょっと遅いよね」という話とか、「検索をダイナミックにしたいね」みたいな話がありました。「使いやすいUI」というのはちょっとぼんやりしているので、課題感としてまずフォーカスしたところが「なんか遅いよね」というところと「検索をダイナミックにしたいね」というところでした。レストランは、フロントエンドをモダンな開発にしていく取り組みの中で、webpackを入れたりJavaScriptをESNextで記述するようになりました。ReactとかVue.jsどうしようか?」みたいな話があって、「フロントエンド改善として、なにかフレームワークを入れたいよね」という話が出ました。レストランの開発体制はこんな感じでした。デザイナーとエンジニアがいるんですが、デザイン領域と設計・実装領域の中で、フロントエンドではHTML・CSS・JavaScriptを、程度の差はあれど、デザイナーもエンジニアも触るという環境でした。CSSに関しては実質的にデザイナーが記述の中心になることがほとんどで、デザイナーさん自身がCSSの中でデザインを調整するようなケースもわりとありました。そんな感じで「デザイナーがコードを書く環境が必要かな」と考えまして、「Vue.jsがいいんじゃないか?」というかたちで検討材料にあがってきました。Vue.jsのいいところは、やはりHTML・CSS・JavaScriptらしい記述ができる単一ファイルコンポーネントであったり、日本語の公式ドキュメントが整備されていたり、技術的ジャンプを少なくモダン開発が進められそうなところでした。レストランのチームはそこまでフロントエンドガチ勢のいるチームではなかったので、導入障壁としてはVue.jsが一番低いんじゃないか、ということでVue.jsを採用しました。Vue.jsで実装した機能それでは、今年までにスマートフォン向けのページでどういったものをVue.jsで実装してきたかを簡単にご紹介します。最初のほうに作られたのがこのナビゲーションドロワーです。ハンバーガーメニューですね。いったものがVue.jsで作られていたり。あと、これは店舗紹介ページ、社内では「ガイド」と呼んでいますが、その中で表示する、販売中のプランの詳細を表示するモーダルです。これは下からヒョイっと出てくる画面なんですけど、こういったものがVue.jsで実装されています。あとこれは、その店舗紹介ページの中の口コミを表示する部分です。ここを押すとアコーディオン状に開いたり閉じたりするところなのですが、こういったものもVue.jsで実装しています。最近では、リアルタイムでキーワードを入力するとサジェスチョンが出て検索に飛べるという、ページ内検索モーダルのようなものもをVue.jsで実装しています。話は戻り検索ページの課題についてです。「なんか遅いよね」というのと「検索をダイナミックにしたいね」という部分に関して、どれぐらい遅かったかという部分、メトリクスで出すとだいたいこんな感じでした。Interactiveに至るまでだいたい10秒ぐらい。これはキャリブレのLighthouseのスコアなんですが、3G回線でこのような状況でした。「けっこう遅いよね」というような感じでした。数字的にも遅いですが、けっこうUX的に課題感がありました。パネルから検索条件を変更すると、SPAなんですがなぜかページリロードから始まって、そのあとページが読み込まれたあとに動的に検索結果をJavaScriptで描画するという、「SPAなのに毎回ページリロードするの無駄じゃない?」という実装になっていました。これをあるべき姿のちゃんとしたSPAにしようということで、検索ページの課題解決というかたちで始めていくことになったのが、2017年末のことです。CTO室が誕生したのがちょうどその頃だったんですが、CTO室の課題としてこれを解決することになりました。技術的にどのように進めていこうかということで、昨今流行りのコンポーネント指向みたいのが出てきますが、その前に「“ちゃんとした”SPAとはなにか?」という部分です。Applicationのことです。最近、デザイン部門でも「越境」というキーワードがわりと流行っているのかなと思っています。Design』の本の中でこんな記述がありました。「理解したり活用するときに、名前のインパクトはすごい大きいよね」という話が出てきました。Design』の序章のにも書いてあり、「なるほどな」と思いました。Webページ開発における関心の分離というのは、いわゆるこういう感じなのではないかと考えています。サーバサイドでHTMLを描画して、クライアント側でCSS・JavaScriptを実行してスタイルを当てて動的な動きをつけるというものかと思います。一方で、Webアプリケーションとなったときに、サーバ側の役割というのはもっとロバストなものになるのかなと思っています。単純に必要なデータを返す。クライアントサイドでViewとなるHTMLやCSS、JavaScriptを構築する。このときに、1つ新たなレイヤーとして登場するのが「コンポーネント」という概念です。Componentを中心にフロントエンドを設計していくのが技術的には正しいアプローチではないかと考えました。レストランには以前からあるWebページも当然存在しているので、「その中に組み込むとしたらどんな感じになるか?」と考えたのがこちらです。サーバサイドでHTMLを描画して、旧来どおりCSS・JavaScriptをクライアントを実行しつつ、コンポーネント指向を取り入れる部分だけクライアントでコンポーネント指向を実装します。いうのももちろん可能かとは思いますが、積極的にやりたい構成ではありません。それをやるならもっとやりやすい方法として、Vueの場合はNuxt.jsがいいのではないかと考えました。NuxtのメリットはVue.jsのコンテキストでコンポーネント指向開発ができて、Vue.jsをベースに技術的ジャンプを抑えてアプリケーション開発というのものに移行できるところです。Configuration」的な考え方で実装もしていけるという感じで、「わりとNuxt.jsいいんじゃないかな?」というのが徐々に自分の中で高まっていきました。SEOとサーバーサイドレンダリングまた話は変わります。レストランは高い成長率を維持するサービスなんですが、その成長率に寄与する大きな要素がSEOです。AMPに対応することで検索結果への露出を高めるような取り組みもしました。レストランの中でやっていますが、そこは強力なデジタルマーケティングチームがいて、売上を牽引しているということが、可能にしています。いったチームがフロントエンド開発にどんなものを求めているかというと、サーバサイドレンダリングです。ページ上の重要なコンテンツは静的コンテンツであることが求められました。これは僕の心の声なんですが、当時SEOチームにいて、Vue.jsでUIのリッチ化をしたいと思いつつ、でも、事業インパクトに関わる部分は動的コンテンツができないので、すこし葛藤がありました。そのため、現実逃避で「本当にSSRしかないのかな?」ということを時々調べたりもしました。2017年の8月ぐらいにGoogleのAdvocateの方がツイートしていたこちらのツイートや。Search」という記事が公開されました。41なので、41が対応している機能はサポートしている」とか、「ES6はサポートしてないけど、JavaScriptは実行できるよ」ということが書いてありました。Renderingにあたるようなヒントが公開されたりしました。いうわけで、動的なコンテンツをSEOフレンドリーにするというような意味でのブレークスルーがありそうでしたが、そうは言っても「やっぱりSSRが安心確実だよね」という流れは社内の中で大きかったです。Vue.jsの実装も徐々に社内では広がりを見せており、SEOを担当するチームでも使ってもらっているような状況でした。そんな時、たまたまSEOチームのデザイナーから「Vue.jsってサーバサイドレンダリングできないのかな?」みたいな話がありました。それで「おっ?」と思って、Nuxt.jsを思い出しました。技術課題と業務課題の合致こういった流れの中で、「コンポーネント指向開発をしたい」という技術的アプローチと「サーバサイドレンダリング」という業務課題、この2つを解決する方法として「Nuxt.jsはわりといいのではないか?」と社内で認知されるようになってきました。話は戻り「ちゃんとしたSPAにしよう!」ということで、「ちゃんとしたSPAとは、こんな感じがいいんじゃないか?」ということで、ちょっと説明させていただきます。いま、新しいバックエンドとしてPythonにリプレイスするという取り組みが進んでいます。これをバックエンドとして、Nuxt.jsを使うためにNode.jsのBFFをやって、クライアントにはVue.js、Nuxt.jsで実装されたコンポーネントを返却するという構成にするのがいいんじゃないかという機運が高まり、検索ページのSPA化にNuxt.jsを採用することを決断しました。コンポーネント指向での設計では、実際にどうやって実装していくかということで、コンポーネント指向設計の話をします。コンポーネントはこのようなイメージで定義しました。いうことかというと、「データ、テンプレート、ロジック、スタイル、それぞれ関連性が深いもの同士をモジュール化する」「ファイルタイプによる縦割りではなく、関連性によるファイルタイプ横断の串刺しでのグループ化」「フロントエンド実装のあらゆるアセットをコンポーネントと捉えて管理する」というかたちで定義しました。「すべてをコンポーネントとして捉えたときに、どんな種類のコンポーネントが必要なのか?」ということが浮き彫りになってきました。プログラム上で必要な粒度としては、2種類に分けてざっくりこんな感じかなと思います。「グローバルなデータ」は定数やグローバル変数みたいなもの。「型定義」はTypeScriptを徐々に取り入れているので、ここで定義するような列挙型とかモデルみたいなもの。「共通もしくはグローバルロジック」はUIコンポーネント間で共通化したいロジックや、パフォーマンス上グローバル化したいロジック。それに加えて、そのViewとなる「UIコンポーネント」が必要だと考えました。スタイル上必要な粒度がこちらです。「グローバルデータ」としてはCSS変数。「フィルター」にあたるようなCSSのミックスインですね。あとは「グローバルスタイル」は、要素型セレクタへ指定するようなスタイルとか。「UIコンポーネント」はCSSで言うと、クラスセレクタに対してどういうスタイルを定義するかという部分になります。いった分類が必要だと考えました。考えていた時にたまたま目についたのが、CSSのアーキテクチャのITCSSです。簡単に言ってしまうと「柔軟で移行しやすい構成」「BEMなどのCSS方法論との互換性」「コンポーネント指向のCSSアーキテクチャ」といった特徴がありました。逆三角形の中に図示された抽象化の中で、適切にレイヤー化していこうという、アーキテクチャです。そのレイヤーの分類としては、ITCSSはこういったレイヤー分けを提唱しています。「Settings」はCSS変数を管理するレイヤー、「Tools」がCSSミックスインを管理するレイヤー、「Generic」がいわゆるリセットCSSを管理するレイヤー。「Elements」は要素型セレクタへのスタイルを管理するレイヤーです。Object系のUIコンポーネントのスタイルを管理するレイヤー。「Components」がユーザーに機能を提供するUIコンポーネントのスタイルを管理するレイヤー。「Trumps」というのが最後にあって、これは例外的な用法で使えるレイヤーです。した7つのレイヤーがデフォルトで提唱されています。レストランの構成を当てはめてみると、このようになりました。「Settings」レイヤーでは、スタイルに関してはCSS変数、プログラム上のものに関しては定数などのデータを扱い、「Tools」レイヤーでは、CSSはミックスイン、フィルター。プログラム上の話では、バリューオブジェクトとかDTOみたいなアプリケーション上の型となる定義を扱い、「Generic」では、グローバルスタイル、もしくはアプリケーション上共通化されたロジックとかグローバルなビジネスロジックを扱います。DesignにおけるAtomsのようなプリミティブなコンポーネントを扱い、「Objects」ではMoleculesのようなコンポーネントを扱い、「Components」レイヤーではOrganismのようなコンポーネントを扱う、というようなかたちで定義してみました。意外とありなんじゃないかと思います。Designの場合はAtomsレイヤーにあたる抽象度がITCSSより粗いというか、細かく分類ができなさそうだと考えました。レストランのフロントエンドのレイヤードアーキテクチャとしてITCSSを採用するに至りました。Storybookでコンポーネントをホスティングこのように、レイヤードアーキテクチャによってコンポーネントを分類したので、それに沿って実装していきました。コンポーネントはUIなので、見た目としてどんなものが作られているのかわからないとつらいので、同時にStorybookでコンポーネントをホスティングするようにしました。レストランのトンマナをCSS変数として定義して、それをStorybookで表示できるようにしました。あとは「Elements」層では、わりとよく使うようなカルーセルの実装をコンポーネント化したりとか。「Object」の層ではいわゆるMolecules的なイメージのコンポーネントを実装したり。「Components」層では単一機能となるような1つの機能を提供するコンポーネントの実装をしたり。このように、徐々にコンポーネントを増やしていきながら検索ページのリニューアルを行っていきました。リニューアルの成果とこれから結果としてどういったものが得られたのかというところでいうと、検索条件を変更するたびに起こっていたページリロードはせずに、もちろん直接、動的に検索結果を描画できるようになりました。スコアとしてもだいぶ改善できました。Byteがもともと4秒だったところが400ms。この前測ったらだいぶ短くなって、自分でもびっくりしました。バックエンドをPythonにしたことも大きいとは思いますが、こういったかたちで改善ができました。Interactiveに至るまでは10秒だったのが8秒というところで、やはりSPAでJavaScriptがヘビーになった部分があり、そこまで劇的には変化が見られなかった部分ではありますが、スピード改善もできました。Indexを計測しました。Indexも改善できました。レストランはWebらしく、プログレッシブに改善を続けていければと思います。ご清聴ありがとうございました。関する記事をまとめています。コミュニティをフォローすることで、株式会社一休に関する新着記事が公開された際に、通知を受け取ることができます。