フロントエンド高速化アプローチ

Updated / Published

そもそも何故ウェブサイトを高速化するのかですが、ウェブサイトを高速化することで

  • 顧客を逃さず機会損失を最小化できる(3秒以上表示にかかると大半のユーザは離脱すると言われている)
  • 利用者のストレスを軽減でき、人とウェブサイト両方のポテンシャルを高められてPVやリピーターの増加へとつながる
  • Googleがサイト表示速度を検索順位アルゴリズムに組み込んでおりSEO対策にも有効である

など、ウェブサイトを高速化することは良いこと尽くしであり、もはやウェブサイトとして存在する以上は避けては通れない必須の実施項目であると言えます。

ウェブサイトを高速化するアプローチは、各ファイルの表示速度を高速化するフロントエンドとサーバやデータベースからのレスポンスを高速化するサーバサイドの2つに大別されます。まず今回は、フロントエンドを高速化するためのアプローチについてまとめてみましたので、具体的な方法を交えながら紹介していきます。

1. CSSの高速化

CSSの高速化の代表的なものには、CSSのセレクタ構文を最適化して高速化する手法とCSSで読み込んでいる画像ファイルを最適化して高速化する手法があります。

1-1. CSSのセレクタ構文を最適化して高速化する

CSSのセレクタ構文の最適化は、モダンブラウザの解析そのものが十分に高速なので、セレクタ構文を最適化したところで実質的な体感速度はほとんど変わるようなことはありません。しかしながらこの後に紹介するJavaScriptの高速化との兼ね合いもあるのでCSSのセレクタ構文の最適化についてはしっかりと理解しておく必要があります。

まず、ブラウザがCSSのセレクタをどのように解析しているかですが、右から左へと解析していることを知っておいてください。つまり、CSSのセレクタはよく上書きするために親要素を書くなどして詳細度を高めて利用しますが、CSSのセレクタを詳細に書けば書くほど上書きの優先順位は上がるもののそれに比例して、ブラウザがそれを解析するために要する時間は増えてしまうことになるのです。

例を紹介します。

div#foo {}   /*高速化においてはBad*/
#foo {}      /*高速化においてはGood*/
div.bar {}   /*高速化においてはBad*/
.bar {}      /*高速化においてはGood*/
#foo .bar {} /*高速化においてはBad*/
.bar {}      /*高速化においてはGood*/
table thead td {}     /*子孫セレクタは高速化においてはかなりBad*/
table > thead > td {} /*子セレクタは子孫セレクタよりはマシだけどBad*/
.thead-cell {}        /*できることならクラスをふるのが高速化においてはGood*/
* {} /*全称セレクタの使用は高速化においてはもっともBad*/

さらに詳しく知りたい方は少々古い記事ですがMozilla Developer NetworkのWriting Efficient CSSの日本語訳がとても参考になります。
https://developer.mozilla.org/ja/Writing_Efficient_CSS

ただし、先にも述べているようにCSSのセレクタを最適化しても、モダンブラウザが備えているセレクタエンジンの解析が十分高速なので体感速度の変化はほとんどありません。そのため既存のウェブサイトに労力を要してまで実施するほどのものではありません。なぜなら、これを実施しようとすると詳細度がいろいろ変化するために上書きの優先順位がバラバラになり、結構な労力を使うことになるのは目に見えているからです。

全くの新規でウェブサイトを作る場合やフルリニューアルをする場合などにセレクタ構文の最適化を気持ち実施していく程度で良いかと思います。大事なのは、ブラウザがCSSのセレクタを右から左へと解析していることを知っておくことです(後に紹介するJavaScriptとの兼ね合いがあるため)。

1-2. CSSで読み込んでいる画像ファイルを最適化して高速化する

次に、CSSで読み込んでいる画像ファイルを最適化する手法についてですが、これは一般的にCSSスプライトと呼ばれているもので、2008年頃から有名になって、もはや広く普及している手法でしょう。ここで言う画像の最適化とは複数の画像を1枚の画像にまとめ、background-positionプロパティ等を上手く活用して適切な位置指定を行うことで、同様の表示をHTTPリクエスト数を減らしながら実現させることです。

たとえば、1KByteの画像5枚を読み込むのに200ミリ秒かかるとしても、これを1枚にまとめて約5KByteの画像1枚にしてしまえば読み込み時間は50ミリ秒に短縮できるなど、先に紹介したCSSのセレクタ構文の最適化よりも、HTTPリクエストの数を大幅に減少させることと1枚にまとめることで画像のファイルサイズ自体も最適化させることが可能なCSSスプライトは高速化において非常に有効です。
ただし、注意しておきたいことがあります。CSSスプライトが高速化に有効だからと言っても全てをなんでもかんでも1枚の画像にまとめてしまうのは良くありません。

先に例で紹介していたGoogleにしても、YouTubeにしても、上手く小さな領域にまとめられていることがわかります。CSSスプライトを行う際には、巨大なファイルサイズのスプライト画像や面積が非常に大きなスプライト画像にならないようにしましょう。
巨大なファイルサイズをもつ、面積が非常に大きなスプライト画像を使ったCSSスプライトを行うとブラウザはレンダリングする際に大量のメモリを消費してしまうことになります。たとえ、スプライト画像の1部の縦10px × 横10pxの領域を表示させているだけであっても、実際にはブラウザではもとの巨大なスプライト画像をレンダリングしているからです。

※参考:巨大なスプライト画像でメモリが大量に消費されることについてはMozillaの開発者であるVladimir Vukićević氏がTo Sprite Or Not To Spriteにて言及されています。
http://blog.vlad1.com/2009/06/22/to-sprite-or-not-to-sprite/

このためCSSスプライトを実施する際は、似たような画像でまとめることや、一度マージしてしまうと以降のメンテナンスが大変になるので拡張性を考えてスプライトする画像をまとめること、縦横のpxをあわせても10000平方px以内に収めるようにするなど一定の法則を設けたスプライト画像を作成することを心がけると良いでしょう。

※参考:CSSスプライト作成ツールなど

2. JavaScriptの高速化

JavaScriptの高速化の代表的なものには、DOMへのアクセスを最適化して高速化する手法と、実行速度がより速いコードへと書き換えて高速化する手法があります。最近のJavaScriptでの開発は、便利なjQueryをベースにすることが圧倒的に多いので、ここでは主にjQueryを交えた高速化手法を紹介します。

2-1. DOMへのアクセスを最適化して高速化する

jQueryと言えば、CSSのセレクタと同様の書式で指定のHTMLノードへとアクセスできるのが大きな魅力ですが、その書き方次第で、指定したHTMLノードへのアクセスは鈍足化してしまったり、また十分に高速化させることも可能です。ブラウザによっては速度が大きく違ってきます。jQueryも当然JavaScriptで書かれているので、セレクタ部分の解析は内部的にはネイティブなJavaScriptのメソッドでHTMLノードへとアクセスしています。まずquerySelectorAll()を試し、それに失敗すると、jQuery1.3からはSizzleという自前のエンジンが走るようになっています。擬似クラスなどの指定があれば、常にSizzleの方が走ることになります。

このjQueryのSizzleエンジンはセレクタの中身を分割し配列に格納して、セレクタの右部から順に調べるようになっています。
つまり、1-1 でCSSのセレクタをブラウザは右から左へと解析していると先述しましたが、jQueryのセレクタエンジンも同じように右から左へと調べているということです。

では、CSSのセレクタ構文の最適化と同じようにシンプルに記述すれば、DOMへのアクセスも最適化できて高速化できるのかと言えば、そう単純ではなくてSizzleの中で使用しているネイティブなJavaScriptのメソッドにも、特にInternet Explorerが対応していないものがあるためにCSSのセレクタ構文の最適化とは異なり例外も多いので、その点も踏まえた例を紹介します。

// IDへのアクセスはCSSと同じで、要素名の記述は余分で鈍足化を招くBadな書き方
$('div#foo')
// IDだけで良い、getElementById()で再帰なしで調べるので非常に高速
$('#foo')
/* クラスだけだと、getElementsByClassName()に対応しないブラウザ(IE9未満etc)は
   全てのHTMLノードからそのクラスの有無を調べるので非常に遅い */
$('.bar')
// 要素を特定すれば、IE9未満もgetElementsByTagName()で絞り込めるので速くなる
$('div.bar')
/* 次のコンテキストを使った書き方は$('#foo').find('div.bar')と同意
   IDは再帰なしで高速に調べることができ、そこからfind()で辿るので
   さらに高速化が図れるかもしれない */
$('div.bar','#foo')

さらに、jQueryはメソッドチェーンと言って一度アクセスしたHTMLノードに続けてメソッドを記述していくことができる非常にすぐれた設計を持っているので、メソッドチェーンを活用することでDOMへのアクセスを効率化できます。

$('#foo').show();
$('#foo').css('color','red');
$('#foo').click(function(){ alert(this.id); });
のように何度も同じ #foo へアクセスするのであれば、次のようにメソッドチェーンで書くことで #foo へは1回のアクセスで済ませながら各メソッドを引き継ぐことができます。
$('#foo').show().css('color','red').click(function(){ alert(this.id); });

さらに、メソッドチェーンにend()メソッドを組み合わせることで、前後のノードや親や子・子孫ノードへのアクセスも効率化できます。たとえば次のようなHTMLがあったとします。

<div class="hoge">
  <div class="bar">…</div>
  <div id="foo">
    <ul>
      <li>リスト</li>
      <li>リスト</li>
    </ul>
  </div>
  <div class="fuga">…</div>
</div>

このHTMLノードの中で最初に#fooへアクセスして、あとは #fooを基点に、#fooの前に位置するノードである div.bar へ、end()で #foo に戻り、#foo の次に位置するノードである div.fuga へ、また end() で #fooに戻り、親のノードである div.hoge へ、またまた end() で戻り、最後に#fooの子孫ノードの2つめの li へとアクセスする例を示します。

$('#foo').css( ... )
  .prev('div.bar').css( ... ).end()
  .next('div.fuga').css( ... ).end()
  .parent('div.hoge').css( ... ).end()
  .find('li:eq(1)').css( ... );

しかしながら、このコードを見てもらえばわかるようにメソッドチェーンやend()メソッドの多用は非常にわかりづらいコードとなってしまいます。そのため、複数人での共同開発を行っている場合やデバッグを重視するような場合にこのような記述は向きません。
そこでおすすめなのがローカル変数にしてしまって、指定のノードへのアクセスをキャッシュ化させることです。

先ほどのメソッドチェーンとend()メソッドを多用した難読なコードも次のようにわかりやすくスッキリ書くことができます。

var foo = $('#foo'); //#fooへのアクセスをキャッシュ化
foo.css( ... );
foo.prev('div.bar').css( ... );
foo.next('div.fuga').css( ... );
foo.parent('div.hoge').css( ... );
$('li:eq(1)',foo).css( ... ); //コンテキストは内部的にはfind()に置き換えられる

また、特にprepend(), append(), before(), after()などのDOMの追加操作を行うメソッドは非常に遅い処理となるため、追加したいDOMをあらかじめローカル変数にしてキャッシュ化しておくことで数倍高速化させることができます。

var foo = $('<div id="foo"/>');
$('body').append(foo);

このためDOMを追加操作するような場合や、何度も同じノードへアクセスすることがあったり、そのノードを基点として前後のノードや親や子・子孫ノードを往来するような場合などはローカル変数にして指定のノードへのアクセスをキャッシュ化させると良いでしょう。

また、ワンポイントとして、指定のノードへのアクセスを高速化するためには、ときにはdiv要素を上手く活用することも大事です。HTML/CSSに精通している人であればあるほど無駄なdiv要素を極力省きたいと思われる傾向が強い(自分にも過去にその傾向があったため)ですが、要所、要所で、よくアクセスすることになるノードにはdiv要素に適切なID名をつけて、そのIDを基点にしたセレクタを記述するようにすれば、JavaScriptの高速化において非常に有効的に活用することができます。

CSSのセレクタ構文の最適化はそれほど効果が得られるものではありませんが、JavaScriptのDOMへのアクセスの最適化は現状のモダンブラウザにおいても十分高速化の効果が得られるのでしっかりと実施していきたいところです。

2-2. 実行速度がより速いコードに書き換える

JavaScriptは書き方をちょっと変えるだけでも実行速度に大きく影響します。よく使うもの中で、より実行速度を速められるコードをいくつか紹介します。

繰り返し処理はjQueryのeachよりもforなどのネイティブな関数を使った方が高速です。また、forを使うときは、forの繰り返し処理内でlengthによるカウントを行うことのないように注意してください。CPU使用率も抑えられます。

for (var i = 0; i < foo.length; i++) {}            // 高速化においてはBad
for (var i = 0, len = foo.length; i < len; i++) {} // 高速化においてはGood

関数にはメソッド名をつけないで済むなら、つけない方が高速です。

var foo = function bar() {} // 高速化においてはBad
var foo = function () {}    // 高速化においてはGood

配列やオブジェクトの初期化にはそれぞれの括弧式を使うのではなくnewを使った方が若干高速です。

var foo = [];          // 新しい配列の生成
var foo = new Array;   // new Array と書いた方がわずかに高速
var hoge = {};         // 新しいオブジェクトの生成
var hoge = new Object; // new Object と書いた方がわずかに高速

最後にこれは高速化と言うよりは、jQueryでのイベント操作の効率化についてですが、テーブルのセルやリスト項目など同様の要素が大量にあるものに対して、$('td').hover( function(){ ... } ); や $('li').click( function(){ ... } ); のようにイベントを設定すると、当然、tdやliの要素の数だけイベントを設定していることになり、それに比例してメモリ消費が増え、動作も重たくなってしまいます。

そこで、イベントが実行されると、イベントを設定していた要素(イベント発生源)から親ノード、次の親ノードへとイベントが伝わっていくイベントバブリングの仕組みを利用して、効率良くイベント設定が行えるdelegate()メソッドを使うようにしましょう。

次のようにdelegate()メソッドを使うことで、設定しているイベントは1つになり、消費メモリが抑えられるので、軽い処理は結果的に高速化へとつながります。

$('table').delegate('td', 'hover', function() {
	$(this).toggleClass('active');
 });

参考:JavaScript及びjQueryの高速化についての記事(リンク先はすべて英語)

3. 転送量削減による高速化

転送量の削減は先に紹介していたCSSの高速化やJavaScriptの高速化よりも比較的簡単に実施することができ、実際の表示速度としても体感速度としても速くなることがもっとも確認しやすいです。手軽に高速化を実現したい人は、この転送量削減による高速化だけでも実施されると良いでしょう。項目も多いので順々に紹介していきます。

なお、3-6 以降の内容はサーバサイドの知識も少々必要になってきます。ここでは、CentOS, Fedora, Red Hat等のLinux OSにウェブサーバはApache 2.x系で、以下で紹介するモジュールが事前に読み込まれていることを前提に話を進めていきます。専用サーバやVPS等root権限をもつサーバを利用している方は /etc/httpd/conf/httpd.conf の中身を確認(cat /etc/httpd/conf/httpd.conf)してください。その他のレンタルサーバを利用されている方はレンタルサーバ会社に問い合わせてみると良いでしょう。

3-1. CSSやJSファイルは外部ファイル化する

CSSやJavaScriptの2回目以降のロードを高速化するために、CSSやJavaScriptはインラインに記述するのではなく、必ず外部ファイル化するようにします。CSSやJavaScriptをインラインに記述しているとページを読み込む度に毎回ロードされてしまいますが、外部化することでブラウザにファイルをキャッシュさせることができるようになります。

3-2. CSSファイルの参照を先にJSファイルの参照は後(最後)に記述する

直接、転送量を削減させることとは関係ありませんが、ブラウザが1つのサイトから同時にダウンロードできる最大数に制限があり、ソースの上にあるものから順にダウンロードしていくため、ファイルの読み込み順は重要です。たとえばHTTP/1.1においてInternet Explorerが1つのサイトから同時接続要求できるファイルの数はデフォルトでは2ファイルに制限する設定になっています。

このため、ウェブサイトの全体的なレイアウトを定義しているCSSファイルを先に読み込むことは、エンドユーザ側の体感速度の飛躍的な向上につながります。最終的な全体のダウンロード時間がかわるものではありませんが、ページ遷移時の真っ白いページが表示されている時間を少しでも短くできればできるほど、エンドユーザ側もちゃんと表示できていると安心でき、高速化の体感は大きく違ってきます。

また、JavaScriptでレイアウトを整えるようなことはあまりないため、JSファイルは</body>の直前などの最後に記述すると良いとされています。JavaScriptで全体的なレイアウトを整えている場合は、CSSファイルの直後に記述して読み込ませると良いでしょう。

3-3. 外部ファイルはできるだけまとめて、容量も極小化(minify)する

複数のCSSファイルや複数のJSファイルが存在する場合は、できるだけそれぞれ1つのCSSファイル・JSファイルにまとめましょう。まとめれば、単純にまとめることができた分のHTTPリクエスト数を減らせることができます。3-2 でも述べたように、同時接続要求できるファイルの数が制限されている点からもファイルをまとめるのは有効です。

また、CSSファイル・JSファイル内には多数のタブやスペース、改行等の空白類文字やコメントが存在していることと思いますが、当然その分ファイルの容量は圧迫されているため、これらを削ってファイルの容量を極小化(minify)させることで転送量を減らすことができます。次のリンクから複数のファイルをまとめてくれて、また余分な空白類文字なども削ってくれるオンラインツールが利用できます(CSS/JS両ファイル対応)。

Online JavaScript/CSS Compression Using YUI Compressor
http://refresh-sf.com/yui/

3-4. サイト内の共通した画像はPNGを使って、ファイルサイズもできるだけ小さくする

使用する画像ファイルのサイズを小さくできれば転送量を削減でき、表示速度の向上につながります。

※参考:Yahoo!ニュース高速化へのサイトデザイン側からのアプローチ
http://techblog.yahoo.co.jp/cat207/cat214/yahoo_3/ の話が有名です。8bit-PNG形式を採用し、減色処理を行い、さらにPNG画像はチャンク(Chunk)と呼ばれる構造を成すデータが集まって成り立っているものですが、使用している画像ツールに依っては補助チャンクや追加公開チャンクなど不要な情報も多く埋め込まれているため、これらの不要なチャンクを削除することでもPNG画像は他の画像フォーマットと比べて大きく容量を削減することが可能です。

次のリンクからPNGファイルの不要なチャンク情報などを削除できるオンラインツールが利用できます。
punyPNG - PNG Compression and Image Optimization
http://punypng.com/

3-5. jQueryファイルなどは予めキャッシュされていることが多いCDNで提供されているものを利用する

CDNは、みんなが使っていれば、多くのエンドユーザがそのファイルをキャッシュ済みであろうという考えのものです(CDNが提供する複数のファイルを利用するのであれば効果はあるかもしれませんが、jQueryのみを利用する等であれば、CDNの利用はそれほど高速化につながるものではないでしょう)。

なお、CDN側のサーバが落ちてしまうと影響がでる場合もあるので、次のようにCDN側のファイルが見つからなかったような場合に、サイト内から同内容のファイルをダウンロードさせるようにする対策もあるようです。

次の例では、googleの提供するCDNから常にjQuery1系の最新バージョンをダウンロードするようにしていますが、これが見つからなかった場合にサイト内からjquery-1.5.2.min.jsのファイルをダウンロードさせます。

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>!window.jQuery && document.write('<script src="/jquery-1.5.2.min.js"><\/script>')</script>

3-6. ファイルをgzip圧縮(deflate)して転送する

mod_deflateモジュールを利用するだけで超高速にgzip圧縮した状態のファイルをエンドユーザに転送することが可能です。

次の例は、HTMLファイルとXMLファイルとCSSファイルとJSファイルの4形式ファイルについてはgzip圧縮して転送するようにした例です。.htaccessまたはhttpd.conf等のサーバ設定ファイルに記述することで、反映させることができます。

# 古いブラウザ(IE/NN)はバグがあるのでtext/htmlのみ圧縮
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscape Navigator4.x系は問題が多いのでこれで完全にオフにする
BrowserMatch ^Mozilla/4.0[678] no-gzip
# IE7,8はMozilla/4だけど問題がないので他のファイルも圧縮対象に戻す
BrowserMatch \bMSIE\s(7|8) !no-gzip !gzip-only-text/htm
# UA(ブラウザ)によって圧縮をかける・かけないを切り分けるのでVaryヘッダを追加
Header append Vary User-Agent
# 圧縮・転送するファイル形式を指定する
AddOutputFilterByType DEFLATE text/html text/xml text/css text/javascript
  • BrowserMatchの部分は圧縮に対して問題を抱えているブラウザを対象からはずすなど上手く対処させるための記述です。no-gzip は圧縮をしない、gzip-only-text/html はMIIMタイプが text/html のときだけ圧縮することを意味する環境変数です。IE7と8はMozilla/4で最初のBrowserMatchの条件に該当してしまいますがIE6におけるdeflateのバグはIE7から改善されているので、圧縮対象となるように調節しています。IE9からはMozilla/5になるため該当しません。
  • Header append Vary User-Agent はブラウザによって圧縮をかけるかかけないかを切り替えるので、Varyヘッダを追加している設定です。
  • AddOutputFilterByType DEFLATE に続いてMIMEタイプを指定していくことで、圧縮したい対象ファイル形式が追加できます。なお、MIMEタイプの追加を行われる際に画像系の追加は不要です(画像はすでに圧縮されているため)。

ただし、mod_deflateモジュールがいくら超高速にgzip圧縮して転送させることが可能といってもリクエストのたびに毎回圧縮・転送しているため、アクセスが多いサイトであればかなりのCPUを消費するなどサーバリソースを蝕むことになります。サーバリソースを有効活用するためにも、手間は少し増えますがCSSとJSファイルは次の3-7で紹介しているように最初からgzip圧縮したファイルを提供されることをおすすめします。

3-7. 静的なCSS/JSファイルのgzip圧縮はgzip圧縮ファイルを作成して負荷軽減

先に 3-6 でmod_deflateモジュールを紹介しましたが、手間は増えますが、できることならCSSファイルやJSファイルはmod_deflateモジュールを使うよりも、あらかじめgzip圧縮したファイルを提供するようにした方がサーバリソースを有効に活用できます。

gzip圧縮ファイルを作成するには、

$ gzip -c hoge.min.js > hoge.min.js.gz

のコマンドで便利に作成できます。ただし、CSS/JSファイルの数が多いような場合は、毎回gzipコマンドを打っている私を見兼ねて、@sackam さんがサクっと作ってくれたgzip圧縮ファイルを作成するPerlプログラムを使われると便利ですので紹介します。指定したディレクトリにあるCSSファイルとJSファイルにマッチし、元のファイルを残したまま、gzip圧縮したファイルを簡単に作成できます。

#!/usr/bin/perl
# Unix/Linux only.
use strict;
# httpd server DOCUMENT_ROOT
my $docroot = '/home/ユーザ名/htdocs'; # 要変更
my @extensionlist = (
    '*.js',
    '*.css');
if($ARGV[0] eq ''){
    $ARGV[0] = $docroot;
}
if($ARGV[0] !~/\/$/){
    # add slash.
    $ARGV[0] .= '/';
}
foreach my $exp(@extensionlist){
    my @filelist = `ls $ARGV[0]$exp`;
    foreach my $file(@filelist){
        chomp $file;
        `gzip -c $file > $file.gz`;
    }
}

上記のPerlのコードをmakegz.plとして、サーバに配置します。配置先は、/usr/bin または /usr/local/bin または ~/bin(/home/ユーザ名/bin) など環境により異なるので、次のコマンドで環境変数のPATHを参照して、

$ echo $PATH

表示されたうちのいずれか適切なところに配置します。ここでは /home/ユーザ名/bin に配置した例として進めます。

次にこのmakegz.plに実行権限を与えます。

$ chmod 700 /home/ユーザ名/bin/makegz.pl

最後に圧縮したいCSSファイルやJSファイルがあるカレントディレクトリで

$ makegz.pl .

とコマンドを実行してやるだけで、gzip圧縮を行ったCSSファイル・JSファイルが作成できます。

gzip圧縮ファイルが作成できれば、gzip圧縮ファイルの方を優先的に読み込むようにmod_rewriteモジュールでgzip圧縮に対応しているUA(ブラウザ)に対してのみリライト処理を掛けます。.htaccessまたはhttpd.conf等のサーバ設定ファイルに次のように記述します。

RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+)(\.css|\.js)$ $1$2.gz [L,QSA]
<FilesMatch "\.css\.gz$">
  ForceType text/css;charset=UTF-8
  AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.js\.gz$">
  ForceType text/javascript;charset=UTF-8
  AddEncoding x-gzip .gz
</FilesMatch>

これでgzip圧縮に対応しているブラウザであれば、

<link rel="stylesheet" href="/common.css" />
<script src="/common.js"></script>
とあるCSSファイルとJSファイルの参照を
<link rel="stylesheet" href="/common.css.gz" />
<script src="/common.js.gz"></script>

へとリライトを行って、gzip圧縮ファイルの方を参照するようになります。

また、.css.gzや.js.gzというファイルを適切にgzip圧縮ファイルとして読み込めるようにFilesMatchディレクティブでマッチさせてMIMEタイプ、文字コード、エンコーディングを設定します。

3-8. キャッシュされたファイルの期限切れ日を設ける

mod_expiresモジュールでは、キャッシュされたファイルを指定した時刻に達するまで、つまりはいつまでキャッシュから取得し続けるのかを指定することができます。滅多に変更することのないようなファイルであれば、かなり先の時刻までキャッシュされ続けるように設定すると良いでしょう。 ExpiresByType に続いて MIMEタイプを指定し、次にいつまでキャッシュするのかの期限を指定します。以下は画像ファイル、CSSファイル、JSファイルについては1ヶ月間、iconファイルについては1年間キャッシュし続けるようにさせる設定例です。.htaccessまたはhttpd.conf等のサーバ設定ファイルに記述します。

ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType image/vnd.microsoft.icon "access plus 1 year"

なお、この設定が行われると、当然、指定期限までキャッシュが有効になるので、CSSファイルやJSファイルに修正を行った際は、

<link rel="stylesheet" href="/common.css?20110427" />
<script src="/common.js?20110427"></script>

のようにファイル名に続いて?+日付などをつけることで再びサーバからダウンロードして、2度目からは再びキャッシュからロードされるようになります。

3-9. ETag(エンティティタグ)を無効にする

ETag(エンティティタグ)とは、ブラウザ側でキャッシュしているファイルと、サーバ側にある実ファイルの内容や更新日などが同じかどうかを比較するための短いデータです。リクエストする度にブラウザは毎回ETagをサーバに送信し、そのたびにサーバ側は実ファイルを調べてETag情報を生成し、比較を行い、ETagが一致していれば、サーバからは304のステータスコードが返るようになっています。ただし、複数マシンで構成しているサーバのような場合であればETagは一致しませんし、そもそもETagを生成すること自体が余計な処理を増やすだけなので、ETagを無効にするようにしましょう。.htaccessまたはhttpd.conf等のサーバ設定ファイルに

FileETag None

と記述するか、mod_headerモジュールが有効であれば

Header unset ETag

と記述しても、ETagを無効にすることができます。

3-10. ここまでのサーバ設定ファイルのまとめ

CSSファイルとJSファイルは、gzip圧縮ファイルで配信するようにした内容としてまとめると.htaccessまたはhttpd.conf等のサーバ設定ファイルは次のような記述にまとまります。

# Gzip Commponents
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch \bMSIE\s(7|8) !no-gzip !gzip-only-text/htm
Header append Vary User-Agent
AddOutputFilterByType DEFLATE text/html text/xml
# Rewrite Gzip Commponents
RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+)(\.css|\.js)$ $1$2.gz [L,QSA]
<FilesMatch "\.css\.gz$">
ForceType text/css;charset=UTF-8
AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.js\.gz$">
ForceType text/javascript;charset=UTF-8
AddEncoding x-gzip .gz
</FilesMatch>
# Expires Header
ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType image/vnd.microsoft.icon "access plus 1 year"
# ETag Off
FileETag None

付記

測定ツールをインストールしよう

これまで紹介してきたことの実施前と施策後の効果を測定できるようにPage SpeedというGoogle製のオープンソースツールをブラウザにインストールしましょう。次の手順でGoogle Chromeに試験運用の拡張機能であるPage Speedをインストールできます。

  1. アドレスバーに about:flags と入力し、Enter
  2. 試験運用版の拡張機能 APIの欄を有効にする
  3. Chromeを一旦終了し、再起動する
  4. http://code.google.com/intl/ja/speed/page-speed/docs/using_chrome.html のページの「click here to install Page Speed for Chrome」リンクよりダウンロードする
  5. インストールが完了すれば開発/管理にあるデベロッパーツールに PageSpeedが追加されているのが確認できる

参考サイトや書籍など