読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

プログラミングの話や自分の考えを色々と書いています。特にperl、emacsや読んだ本の話が多いです。

フロントエンド速度改善でやったこと(Expiresヘッダ、faviconのgzip圧縮、JSの読み込み遅延化)

tech

フロントエンド速度改善をしようとして参考にしたもの - $shibayu36->blog; という記事を以前に書いたのだけど、結局何をやったか書いて欲しいと社内で言われたので、今回のフロントエンドの速度改善でやったことについて書いてみる。そこまで大したことはやってないので参考程度にどうぞ。

前提

ページのレンダリングが遅いと思い始めたので改善をすることになったのだが、改善をし始めたところChromeのアップデートがあり爆速になってしまった(FirefoxSafari等はもともと速かった)ので、では明らかにやったほうが良いことだけやりますかという話になった。そのためあんまりbefore/afterもちゃんと取っていないので、今回はやったことの紹介くらいに留める。

やったこと

計測や調査をしてみたところ、以下のようなことはやってしまったほうが良いということになった。

  • 静的ファイルに適切にExpiresを付ける
    • 理由): 改善前はLast-ModifiedやETagしか付いていなかったため、304は返るもののリクエストが飛んでしまったため。そもそもリクエストを飛ばないようにしたい
  • faviconやWebフォントのもgzipで転送する
    • 理由): 基本的にgzip圧縮をかけていたのだが、faviconとWebフォントは漏れていて、特にfaviconの転送量が意外と大きかったため
  • JSの読み込みをbodyの一番下に or async属性を付ける
    • 理由): JS読み込み完了までレンダリングがブロックされるため、読み込みを遅らせたい

静的ファイルに適切にExpiresを付ける

改善前の状態

今運用しているサイトでは、画像やCSS、JSなどの静的ファイルのURLには必ずファイルの中身のdigest値がクエリに付くようにしている。例えば、 /css/app.css?p45XAdRzAntl のような感じ。

ファイルの中身のdigest値がURLに入るということは、内容が変われば必ずURLが異なるということである。内容が変わった時は必ずURLは変わるので、配信時にはExpiresを遠い未来に設定しても、ファイル変更時に古いキャッシュを見てしまうということはない。なので、全ての静的ファイルに適当に遠い未来のExpiresを設定しておけば良い。

しかし、改善前は設定を忘れていて、Last-ModifiedとETagのみ返してしまっていて、Expiresがつかない状態になっていた。

改善案

作戦としては、以下のようにした。

  • 静的ファイルにはExpiresを無条件につけたら良い。期間はある程度長ければ適当で良いが、ひとまず30日くらいとする。
  • Last-ModifiedやETagに関しては、プロキシサーバごとにずれる問題が面倒だし、Expiresが正しく付けば不要と判断。それぞれ消しておくことにする。

プロキシとしてnginxを利用していたので、以下の様な設定を入れた。

location ~ ^/(css|js|images|font)/ {
    # expiresは30日
    expires 30d;

    # etagはdefaultでonなのでoffに
    etag off;

    # if_modified_sinceは無視し、Last-Modifiedも消しておく
    if_modified_since  off;
    add_header Last-Modified "";

    root /path/to/static;
}

参考

補足

静的ファイルのURLにdigest値を入れるのは、テンプレートエンジンでURLを生成する時にメソッドを通す & css内のURLはgulpでビルド時に置き換える、ということをやっているのだけど、これに関してはまた機会があったら方法について書こうと思う。

faviconやWebフォントのもgzipで転送する

改善前の状態

基本的にはコンテンツはgzipで転送すればよい(ただし既に圧縮している画像ファイル等は除く)、と思っていたので、基本的なgzipの転送設定はされていた。しかし、その中でfaviconやWebフォントのgzip転送の設定が漏れていた。

特にfaviconに関しては 複数サイズも一つのファイルで!マルチアイコンの「favicon(ファビコン)」作成方法 | Web制作会社スタイル のように、最近はマルチサイズで保存していることも多く、容量が意外と大きくなったりする。今回の場合もfaviconが数百KBで転送されており、上記のExpiresによってクライアントにキャッシュされるとはいえ、容量が大きいということになった。

改善案

実際にfaviconのファイルをgzip圧縮をかけてみると、数百KBのファイルが数KB程度まで圧縮できた。またgoogleのfavicongzipで圧縮されて転送されており、設定も簡単なのでfaviconとWebフォントも普通にgzip圧縮をかけて転送しても問題なかろうと判断した。

そこでnginxでgzip圧縮を有効にする - ふわふわクラウド のようにgzip圧縮の設定をした上で(正確にはサーバに合わせたgzip_buffers等の設定を適切に行った上で)、gzip_typesにfaviconやWebフォントを追加した。

例えばこんな感じ。よくある設定にapplication/font-sfntやapplication/font-woff、image/x-iconを追加している。

gzip_types text/css application/x-javascript text/javascript application/atom+xml application/rss+xml application/xml application/json application/font-sfnt application/font-woff image/x-icon

参考

JSの読み込みをbodyの一番下に、もしくはasync属性を付ける

改善前の状態

普通にheadタグ内でJSを同期的に読み込んでいた。読み込み完了するまでレンダリングが停止するので、まあ普通にbodyの一番下に持ってくるか、PageSpeed Insightsに指摘される通り、async属性をつけたほうが良いよねということになった。

改善案

とりあえず一番下に持ってきたらいいよねということになった。しかし一つだけ問題があって、読み込むスクリプト(例えばjQuery)に依存するちょっとしたコードを、scriptタグでインラインで書いている部分があり、単に一番下に持ってきたらエラーが出るようになってしまった。

こんな感じ

<head>
  <script src="/js/app.js"></script>
</head>
<body>
  <script>
    $(document).ready(function () {
       ...
    });
  </script>
</body>
  • インラインのJSが、外部から読み込んでいるJSに依存していることがあんまり良くない
  • そんなに大したコードは書かれていない

という理由から、jQueryのコードを標準APIのコードに書き換えた。

書き換えのためには以下のような記事やサイトを参考にした。

書き換えが終わった後は普通にbodyの一番下に読み込みを移動。

<body>
  ...
  <script src="/js/app.js"></script>
</body>

google推奨はasyncなので、そちらにしたほうが良いかもしれないが、あまり検証はしていない。

まとめ

フロントエンド速度改善をしようとして参考にしたもの - $shibayu36->blog; の続きで、実際にやったことについてまとめてみた。本当はちゃんとbefore/afterを計測したほうが良いのだけど、ユーザーが感じる体感がそこまで悪く無さそう & 明らかにやっても良いことはやってしまおうということになったので、ちょっと雑ではあるけど、参考になればと思う。