Ruby
JavaScript
Rails
SinglePageApplication

Railsで高レスポンスかつ初期表示も速いスマホ用Webサイトを作る

More than 3 years have passed since last update.


SPAについて


前書き

スマホはネイティブアプリを使う機会が多いため、すばやいレスポンスが当たり前の感覚になっています。

Webサイトでも同じような感覚を実現できるよう、SPAという仕組みで構築することが昨今の流行となっています。

Rails4ではレスポンス向上のためにturbolinksというSPAに近い仕組みが用意されていますが、簡単に実現できるかわりに、パフォーマンス改善も限定的になってしまうため、ここではSPAを取り上げます。


SPAとは

SPAとはSinglePageApplicationの略で、最初にWebサーバからHTMLを取得した後は、JavaScriptで画面を切り替えていき、JavaScriptライブラリやCSSのLoadingやParseを最初のHTML取得時のみにして、さらにサーバとの通信も最小限とすることで、ネイティブアプリと変わらないような快適なレスポンスを実現する仕組みです。

最近はSPAのフレームワークとしてAngularJSが取り上げられることが多くなりました。


SPAの問題点

SPAを使うことで、ネイティブアプリと遜色ない高レスポンスのWebアプリを構築することができますが、すべての画面の処理を記述したJavaScriptにより、jsファイルが大きくなって、Loading & Parseに時間がかかることにより、初期表示が遅くなってしまいがちです。

特にスマホの場合、PCと比べて回線も遅く、処理能力も落ちるため、より初期表示の遅さが問題になりやすいです。

SPAなんだから、初期表示遅いのは仕方ないよねと諦めてしまいがちなのですが、サイト表示が2秒遅いだけで直帰率は50%増加!の記事にもあるように、折角SPAで快適なページ遷移を実現しても、ページを遷移するまでそのサイトがSPAかどうかは誰もわからないため、初期表示の段階でこのサイト重いと認識されると直帰されてしまいます。


SPAでも初期表示を速くする対応


JavaScriptをできるだけコンパクトになるようにする

下記のようにフレームワークやライブラリをコンパクトなものを選択したり、さらには必要最小限になるよう自分で実装するなど頑張る必要があります。

SPAフレームワーク
サイズ

AngularJS
108Kb

Backbone+Underscore
11.7Kb

ライブラリ
サイズ

jQuery
82Kb

Zepto(jQuery互換)
25Kb


初期表示時にサーバとの通信を極力行わない

SPAで作成する場合、各モデルごとにサーバとデータを同期する必要がありますが、データを初期のHTMLにJSONデータとして埋め込んでしまうことで、Ajax通信を回避することが可能です。すべてのモデルで行うとサーバ自体の処理が重くなったり、ページのサイズが大きくなってしまうので、初期表示に必要な分だけのデータが望ましいです。


index.html.erb

<script type="text/javascript">

var initialData = <%= raw(@initialData.to_json) %>
</script>


jsファイルを分割してOn Demand Loadingにする

パフォーマンス改善の対策として、全部の処理をひとつのjsにまとめることでリクエスト数を減らすことが一般的ですが、SPAの場合、全部の画面の処理をjsにまとめてしまうと、機能追加する度にjsファイルが大きくなってサイトが重くなってしまい、せっかく軽いライブラリを使っていてもその効果が薄くなってしまいます。

そのため、最初は必要最低限のjsだけ読み込んで、以降は必要に応じてjsを動的に読み込むことで、jsのサイズを抑え、初期表示を早くすることができます。

Railsの場合はsprocketsを使って簡単にjsのグループ分けすることができます。On Demandのjsのロードは下記のように実装することができます。


js_loader.js

JsLoader = function(srcMap){

this.srcMap = {};
for(key in srcMap){
this.srcMap[key] = {state: "unload",src: srcMap[key],cb: []}
}
};
JsLoader.prototype = {
load: function(m,cb){
var that = this;
if(!this.srcMap[m]){
throw m + " was not found";
}
if(this.srcMap[m].state == "unload"){
var fjs = document.getElementsByTagName("script")[0]
var script = document.createElement("script");
this.srcMap[m].state = "loading"
that.srcMap[m].cb.push(cb);
script.onload = function(){
that.srcMap[m].state = "loaded"
while(that.srcMap[m].cb.length > 0){
that.srcMap[m].cb[0]();
that.srcMap[m].cb.shift()
}
}
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState=="complete"){
that.srcMap[m].state = "loaded"
while(that.srcMap[m].cb.length > 0){
that.srcMap[m].cb[0]();
that.srcMap[m].cb.shift()
}
}
};
script.src = this.srcMap[m].src;
fjs.parentNode.insertBefore(script,fjs);
}else if(this.srcMap[m].state == "loading"){
this.srcMap[key].cb.push(cb);
}else{
cb()
}
}
}



application.js

//sprockets Topページ表示に最低限必要なjs

//= require jquery
//= require ./lib/underscore
//= require ./lib/backbone
//= require ./lib/js_loader.js
//= require_tree ./templates
//= require_tree ./backbone


misc.js

//sprockets それ以外のページ表示に必要なjs

//= require_tree ./misc_templates
//= require_tree ./misc_backbone


sample_controller.rb

class SampleController < ApplicationController

def index
@src_map = {"misc" => ActionController::Base.helpers.asset_path("misc.js")}
end
end


index.html.erb

<html>

<head>
<meta charset="UTF-8">
<%= layout_meta_tags %>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<title>Sample</title>
</head>
<body>
<script type="text/javascript">
var router = new SampleRouter({jsLoader: new JsLoader(<%= raw(@src_map.to_json) %>)});
</script>
</body>
</html>


router.js

var SampleRouter = Backbone.Router.extend({

initialize: function(options){
this.jsLoader = options.jsLoader
},
routes: {
"topics": "topics",
".*": "top"
},
top: function(){
//描画
},
topics: function(){
this.jsLoader.load("misc",function(){
//描画
}.bind(this))
}
});


まとめ

私が今、開発、運用している無料のFX情報サイトであるsmartFX(smartfx.jp)には、上記の3つの方法を実践しています。

そこそこ大きいSPAのサイトですが、SPAではないサイトに負けない初期表示の早さが実現できていると思っているので、ぜひ見てみてください。