JavaScript
Backbone.js
SinglePageApplication

SinglePageApplicationにおける問題点と対応

More than 3 years have passed since last update.

はじめに

スマートフォン向けFX情報サイトSmartFXをBackbone.jsを使ったSPA(Single Page Application)で運用しています。
そこでの気づいた問題点とその対応方法を共有したいと思います。

直帰率が高くなりやすい!

SPAにより、各ページの遷移が一瞬でできるようにしたのに、最初のうちは約半数の人がTopページのみで離脱してていました。

理由なのですが、2つあると思っています。

SPAに対する認識

そもそもSPAって普通の人は知らないし、知っていたとしても見ためは普通のWebページとなんら変わらなければ、SPAと知らずページ遷移に対して待たされると思ってしまうということです。

対策

リンクをボタンっぽくすることでネイティブ感を出して、通常のWebとは違いますよ感をアピールしてみました。

結果

ほとんど変わりませんでした。ただ、バーチャルトレード機能に関しては、別アプリにして見た目も徹底的にネイティブ感を出すことで直帰率はほとんどなくなりましたが、そもそもバーチャルトレード機能は無料とはいえ、ログイン必須なので、そもそものモチベーションが違うのでなんとも言えません。

初期と現在とバーチャルトレード
初期 現在 バーチャルトレード

初期ロードが遅い

SPAを使うと初期ページのロード時間が長くなることにより、ページ読み込みの途中で離脱してしまったり、最初のロードが遅い印象のためリンクに対する抵抗感を強めてしまっています。
初期ロードが遅い理由は下記の2つが考えられます。

1.データを別々にAjaxで取得

各CollectionやModelがRESTを使ってデータをAjaxで取得するので、リクエスト数が多くて遅くなる上に、ブラウザの同じサイトに対しての同時リクエスト数制限にひっかかります。

2.ひとつのJSファイルに各画面の処理やtemplate情報がつまっているためJSファイルが大きくなりがち。

リクエスト数を減らすためにjsを結合してひとつのjsにしますが、そのjsに全部の画面の処理やtemplateがつまっているためです。

対策

1.に関しては、初期画面のHTMLにAjaxで取得予定のデータを予め埋め込んでしまい、初期データとして使うことで初期画面表示時のAjax呼び出しをなくしました。
2.に関してはもともと意識していて、できるだけ外部ライブラリを使わず、最小限の実装になるよう、自前で実装しました。また、Backbone.jsはサイズがとても小さいので、そこも助かっています。

結果

直帰率は2割近く減少しました(50%->30%)
実は最初はロード時間に問題があると感じませんでした。
なぜなら自分のテスト端末がiPhone5Sだったため、回線がLTEだったりCPUの性能がいいため、2〜3秒で読み込みが終わっていたからです。
直帰率が高すぎることの調査としてiPhone4などでテストしたところ最初の読み込みに10秒近くかかり問題に気づきました。
上記対応によりiPhone5Sでも気持ち速くなり(1〜2秒)、iPhone4でも最初のアクセスは6〜7秒ですが、キャッシュが効く2回目以降は3秒ぐらいで表示されるようになりました。

30%近くの離脱者の方に対してもチップでここを押すとチャートが見れるよとか出してみているのですが、あんまり変わりません。なかなか難しい。

検索エンジンにindexされない!

SPAの画面描画はJavaScriptで行われるため、ほぼ空のページでindexされます。
またindexされたページがTopページやプライバシーポリシーページ等数ページでした。

対応

1.div等で作ったボタンに対してAタグではさむようにする

DIVやLIのclickやTouchのイベントに対して、Backbone.jsのnavigateメソッドを呼んで画面遷移を行っていたのですが、それだとクローラがリンクとして認識してくれないので、Aタグで囲み、Aタグのクリックに対してイベントを拾いつつも本来の動作をキャンセルして自身でnavigateを呼ぶようにしました。

2. 静的ページジェネレータを用意

Googleのbotは、Ajaxページの対策として<meta name="fragment" content="!">のmetaタグがあるページに対して、escaped_fragment=の後に#!以降の文字列がついたURLを使って再度アクセスし直し、その際に返ってくるページをindexするという仕組みがあります。
(pushstateを使っている場合はescaped_fragment=のみ。)
なのでSEO for single page appsを参考に、動的ページをPhantom.jsを使って静的ページに変換するサーバを作り、nginxでescaped_fragment=がついている場合はそのサーバに向けるようにしました。
静的ページ版 SmartFXにアクセスしてページのソースを表示にすると静的ページ化されていることがわかると思います。
1.の対策と合わせて、ページ内のリンクをクローリングしてくれるようになりました。

3.SiteMapを作る

とはいえ、このサイトの特性上、常に被リンクがあるわけではないページも存在するので、sitemapを作り、そのページもindex対象に含めるようにしました。

結果

数ページだったindex数が、ほぼsitemap通りの360件になりました。
また、"チャート 一覧" というキーワードで20位になりました!!

ただ、残念ながらSmartFXは特殊事情により、基本的に検索順位は低いです。
ある事情とは、ニュースや指標等の情報がみんなの外為 という自社サイトと同じなため、Googleの同じ情報はインターネットにひとつでいいというポリシーのもとindex化されません。
みんなの外為とサイトを共通化すればよかったのですが、SmartFXは、TwitterやFacebookを使ってソーシャルログインできるようにするため、アカウントをみんなの外為と別にしたり、バーチャルトレードや投資管理など、みんなの外為にない機能があるので、別サイトのほうがいいだろうと判断した背景があります。
また、Twitter likeなページがあり、そこに自身のバーチャルトレードの結果を自動Tweetしたり、指標の結果を自動Tweetして、関連ページへのリンクをはっているのですが、それらはGoogleの自動作成文章がある場合やページ内に大量のリンクがある場合は質の低いページとして見なすの項目に該当していると思います。
さらにスマホサイトとしてできるだけ説明分をつけず、画像やアイコン等で説明しているため、チャートがあるページなのにチャートというキーワードを取ってくれなかったりします。
なのでサイトを気にいっていただけた際は、 TwitterやFacebookで拡散 していただけると嬉しいです。

PVが取れない!

SPAはその名の通り、最初の一度だけサーバからHTMLを取得し、以降はJSで画面をレンダリングすることで画面を切り替えるため、画面遷移ごとにWebサーバにアクセスしません。そのためwebサーバのログから一般的なPV(Page View)が割り出せません。
サーバ上にはAjaxでのアクセスログばかりになります。

対応

Backbone.jsのnavigateをオーバーロードして、navigateが呼ばれた際は、ajaxで非同期に
サーバにnavigate先のURLにprefixをつけて送りつけ、WebサーバでそのprefixのPathに対して固定の小さなのデータを返すことで画面ごとのアクセスログを取れるように対応しました。

結果

Fluentd + ElasticSearch + kibanaを使えば、フィルタリング機能が使えるので、prefixでフィルタリングをすれば、一般的なPV値がリアルタイムで出力され、各画面ごとのPV値もランキングできるので、今どのページが人気なのかがすぐに分かるようになりました。

まとめ

SPAで作れば、アクセス増大、リピータ激増!!という風に考えていた時期が私にもありました。
現実はなかなか厳しいです。
ただ、GoogleのBotもJavaScriptを解析してくれるようになったので、快適に動くことがサイトの評価につながるようにそのうちなるかもしれません。
追記:
スマホ用のSingle Page Applicationサイトを作る上での問題点と対応も書きました。