PageSpeed Insightsに則したパフォーマンスを出すために必要な設定

headタグ内にcssとjsファイル読み込みを詰め込んでいる皆さん、こんにちは。

GoogleがSEO評価の指標にページの読み込み速度を組み込んだと発表したのが2010年の春先でしたね。PageSpeed Insightsという評価サイトも用意され、自サイトがどのように評価されるかをスコアとして確認できます。

Google – PageSpeed Insights

ウェブを早くしよう!ということで世界中のサイトが表示高速化に向けて邁進しており、その歴史の中で今までのサイト制作の常識も変わりました。そこら辺の変化と、PageSpeed Insightsで評価基準とされている項目への対応方法、また対応できない(しない方がいい)箇所の説明です。

基本的には、サイトを評価した時に問題箇所の説明が出るのでそれを改善していけばいいです。

Googleが用意してあるルールは簡潔で分かりやすいので一度目を通しておくといいでしょう。
PageSpeed Insights のルール

圧縮を有効にする

ウェブサーバーから送信されるコンテンツがgzip圧縮されてない場合に警告されます。

最近はレンタルサーバーでも圧縮機能がONになっていたり、ApacheやNginxなどの一般的なウェブサーバーならそれ用のモジュールが用意されているので設定ファイルを用意して機能を有効化します。

Apache場合はmod_deflateモジュールが用意されているので以下ファイルに設定を書きます。

レンタルサーバーでmod_deflateが有効にな場合は.htaccessに圧縮ルールを書いて対応することができます。

$ sudo vi /etc/httpd/conf.d/deflate.conf

対応していないブラウザへの記述や、圧縮するMIMEタイプを個別に指定するとこんな感じ。

<IfModule deflate_module>
    DeflateCompressionLevel 1
    SetOutputFilter DEFLATE
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ico) no-gzip dont-vary
    SetEnvIfNoCase Request_URI _\.utxt$ no-gzip
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/atom_xml
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/x-javascript
    AddOutputFilterByType DEFLATE application/x-httpd-php
    Header append Vary Accept-Encoding env=!dont-vary
</IfModule>

もう対応しなくてもいいかな、という古い記述もあるので必要に応じて調整を。

ブラウザのキャッシュを活用する

JS、CSS、画像、その他のバイナリファイル(メディアファイル、PDFなど)は基本的に常に変更されてるものではないのでブラウザ側でキャッシュさせろ、という指示です。

何もしなくてもブラウザが勝手にキャッシュしてくれますが、明示的に指示することで1週間、1ヶ月、1年といった細かい期間まで指定できるようになります。推奨されているのは1週間以上、1年以上はRFCのガイドラインに違反するので設定しないように。

これも各種ウェブサーバーに必要なモジュールが用意されています。Apacheの場合は .htaccess でも設定できるのでWordPressなどを利用している場合にも設定が可能です。

Apacheの場合はmod_expiresモジュールを使用。

.htaccessにWordPressっぽい書き方をすると以下のような感じ。期間は各自の環境に合わせて。基本的に1年以内で長く設定できるなら長い方がいいです。

# BEGIN Browser cache
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/html "access plus 1 weeks"
    ExpiresByType text/css "access plus 1 weeks"
    ExpiresByType text/javascript "access plus 1 weeks"
    ExpiresByType application/x-javascript "access plus 1 weeks"
    # Image
    ExpiresByType image/gif "access plus 1 month"
    ExpiresByType image/jpg "access plus 1 month"
    ExpiresByType image/jpeg "access plus 1 month"
    ExpiresByType image/png "access plus 1 month"
    ExpiresByType image/x-icon "access plus 1 month"
    # Font
    ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
    ExpiresByType application/x-font-ttf "access plus 1 year"
    ExpiresByType application/x-font-opentype "access plus 1 year"
    ExpiresByType application/x-font-woff "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# END Browser cache

この指定は外部サーバーには適用されないので、外部から読み込んでいるGoogle Analyticsのタグ(キャッシュ指定2時間)などはどうしようもありません。Tag ManagerやAnalyticsといったGoogleサービスのタグにも突っ込んでくるので使っているなら100点は諦めてください。

そしてキャッシュを有効にさせると、ファイルを更新したのにキャッシュから読み込むので変更が反映されない、という問題が発生します。

開発中であればブラウザのスーパーリロード(キャッシュを全て破棄して再読込、ブラウザ毎にショートカットが違う)を使えば問題ないですが、公開してしまうとそうはいかないので問題がある場合はフィンガープリントを付けてファイル名を変える方法を使います。

フィンガープリントでファイル名を一意にする

jsやcssのファイル名に特定の文字列を付与することで、キャッシュを効かせない手法を使用します。「app.css」というファイルの名前を「app-20160101.css」という形に変更します。手動変更だとHTMLの読み込みファイル名も同時に変更する必要があります。

ファイルに自動でフィンガープリントを付けて読み込みファイル名も変更する、という動きを自動化するにはRailsのAsset Pipelineのような仕組みか、gulpのようなタスクランナー(ビルドツール)が必要です(Laravel Elixirもgulpのラッパー)。

ファイル名の後ろに「app.css?20160101」と付ける方法は毎回ファイルを読み直すので未反映問題は起きませんが、逆にキャッシュが効かなくなるので推奨されていません。

レンダリングを妨げる JavaScript を削除する

htmlのhead内で読み込んでいるJavaScriptは</body>の直前に書けばいいです。理由は「先頭にJS読み込みがあるとそれ以下のコンテンツ読み込みが止まるから」です。HTMLの読み込みより先に実行させたい内容が無ければ最後でいいです。(head内に書きたければ非同期にするしかない)

その上で、非同期で読み込みが可能ならHTML5で追加されたasync属性を付けます。

<script async src="app.js">

そして何も考えずに読み込むJS全てをasyncにしてハマる人が続出するわけです。

<script async src="jquery.js">
<script async src="app.js">

非同期ということは読み込み順序が保証されないので上記のような場合、jQueryよりもapp.js(jQueryを使う処理が書いてある)が先に読み込まれて動かない、という状態になります。

asyncをつけられるのは読み込み順序に関係ないファイルのみです。Google Analyticsのタグなどは完全に独立しているので非同期で読み込まれるようになっています。

ライブラリなどを使っている場合は、gulpなど(手動でもいい)で全てのjsファイルを1ファイルに纏めるしか方法はありません。それが出来ないならこの項目は諦めましょう。

ブロッキングCSSリソースを排除する

「スクロールせずに見えるコンテンツのレンダリングをブロックしている JavaScript/CSS を排除する」
ここはサイトによって調整が必要な内容です。

JSと同じくHTMLより先にCSSを読み込んでることを警告されます。同じようにタグを</body>の直前に移動すれば警告は無くなりますが、CSSが最後に適用されるという状態になります。つまり、最初に素のHTMLが表示されて、一瞬遅れてCSS適用後のデザインになるわけです。

これは普通に見栄えが悪いし使いにくいので、オススメはメインCSSはhead内で読み込み、遅れて読み込んでも支障がない「Font Awesome」のCSSなどはbody直前に移動します。

リソース(HTML、CSS、JavaScript)を圧縮する

HTMLは動的に出力される環境も多いためここでは割愛。改行やスペースが多すぎなければ指摘はされないと思います。

CSS、JSは鋭く突っ込まれるので可能な限り1ファイルに纏めて圧縮します。

圧縮方法はCSSとJSによって色々な方法がありますが、自動で圧縮させる環境が作れなければ以下のようなサイトを使い手動で圧縮するしかないです。

Online JavaScript/CSS/HTML Compressor

CSSファイルの結合と圧縮にオススメの方法

  • Compassを使う
  • gulpを使う

自動圧縮したいならCompassかgulpがオススメ。Compassなら他ファイルのincludeが可能で、出力設定を「output_style = :compressed」にしておけば圧縮済で出力されるので非常に楽。

JSファイルの結合と圧縮にオススメの方法

  • gulpを使う

圧縮ツールはいくつかあるのでYUI Compressorなどの圧縮ツールを使ったことある人はこの辺の説明は必要ないかも。

gulpを使い複数ファイルを1つに纏めて圧縮する場合のgulpfile.jsはこんな感じです。

'use strict';
var gulp = require('gulp');
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");

var jsPath = "/path/to/js_source";

var src = [
    jsPath + "/jquery.min.js",
    jsPath + "/apecell.js"
];

// concat and uglify javascript files
gulp.task("js", function() {
    return gulp.src(src)
        .pipe(concat('app.min.js'))
        .pipe(uglify({ preserveComments: 'some' }))
        .pipe(gulp.dest(jsPath + '/../'));
});

// command: gulp watch
gulp.task('watch', function() {
    gulp.watch(jsPath + '/*.js', ['js']);
});

concat, uglify, renameのプラグインが必要になるのでインストールが必要。

uglifyする時に「preserveComments: ‘some’」とすることでライセンス表示のコメントは残すことが可能(!を追加したコメントが残る)なのでライブラリを結合するなら絶対指定すること。

フィンガープリントを自動付与させる場合は「gulp-rev」を使いフィンガープリントを付与したファイル名を出力し、「gulp-rev-replace」でHTMLの読み込みファイルを変更する、という方法になります。他にも似たようなプラグインがいくつもあるので環境に合わせて使うといいでしょう。

まとめ

圧縮などを自動化する環境を構築しなければ、手動でもほどんどの対応は可能です。個人サイトやWordPressの個人ブログなどであれば手動対応でも十分でしょう。

複数人で開発する場合や、業務として作業効率の向上やスピードを確保する場合には自動化は必須です。ただ、この辺のツールはまだまだトレンドの移り変わりが激しく、Grunt、gulp、Bower等々、覚えることが多すぎるため、逆に非効率にならないためにも吟味してください。

gulpはGruntの不満点を解消するような形で公開されたため、使いやすく人気も高いので今のところはオススメです。