おはこんばんちは、渋谷でお掃除してる人です。
はじめに
自社サービス、ランディングページ、企業ホームページのようなWebサイト(Webページ)において何が一番大切かと聞かれれば、その答えは表示速度です。
ええ、仰りたいことは大いにわかります。もちろんコンテンツも大事です。
ですが、どれだけコンテンツが良くて魅力的な商品だったとしても、表示されなければ意味がありません。
人間という生き物は、Webページが表示されるまでたったの2秒しか待てません。
ウルトラマンでさえ3分間も待てるこのご時世に、たったの2秒です。
その2秒によって、汗水流して作りあげたみなさんのコンテンツが人目につくこともなく、消えていくのです。
というわけで
Webページを高速化する上で大切なことをちょっとだけ紹介します。
この記事を読み終わる頃には、ちょっとだけ高速化に詳しくなって、ちょっとだけPageSpeed Insightsで高得点を取れるようになるかと思います。
早速やって行きましょう。
Webページが表示される仕組み
どうやってWebページが表示されてるの?って思ったまま読んで頂くのも大変申し訳ないので、ちょっとだけ説明します。
Webページが表示されるまでの大まかな流れは
- ブラウザからサーバーへファイルを要求(リクエスト)
- サーバーがブラウザからの要求に応じてファイルを返却(レスポンス)
たったのこれだけです。(DNSサーバーとかの話は割愛)
上記の流れを超簡単に図にするとこのような感じです。
超簡単ですね。
また、リクエストから表示されるまでの流れをもう少しだけ加えると、このような感じです。
上記のようにして、僕たち私たちはパソコンのブラウザ上でWebページを見ることができています。
実際は表示するまでに何度もリクエストを飛ばしているわけですが、その辺はマサカリ飛ばさないでほしいです
Webページの高速化
では、Webページをどのように高速化してくのかという話に移りましょう。
高速化していくためには、最適化をする必要があります。
英語でいうとOptimizationです。テストに出るので覚えましょう。
まさに最適化をすることで、Webページは高速で表示され、閲覧する人全てがハッピーになります。
そこで、今回はネットワーク処理と描画処理の2つに分けて見て行きます。
最適化のポイント
具体的な最適化の方法を紹介する前に、ネットワーク処理/描画処理とは?っていう心の声が聞こえてしまったのでちょっとだけ補足しておきます。
ネットワーク処理
今回は便宜上、描画処理以外で色々やってることっていうことにしておきます。
深くは考えないようにしてください。私は深く考えられないタイプの人間なので。
描画処理
Webサーバーにリクエストを投げて、受け取ったレスポンスからWebページを描画する処理です。
Webページが表示されるまでに、以下の工程を踏んでいます。
- DOM/CSSOMツリーの構築
- DOM/CSSOMツリーからレンダーツリーの構築
- レイアウトの決定
- ペイント(描画)
上記のようにレンダリング処理などを行い、そして、JavaScriptによってDOMツリーにあーだこーだとスクリプト処理が行われます。
最適化の方法
ネットワーク処理の最適化の方法は以下の3つを紹介します。
- リソースの最適化
- リクエスト数の最適化
- クリティカルレンダリングパスの最適化
また、描画処理の最適化の方法は以下の2つを紹介します。
- レイアウト計算/スクリプト処理の最適化
- メモリの最適化
リソースの最適化
JS/CSS/Imageの最適化
JavaScriptとCSSに関してはMinifyします。ミニファイです。
.min.js
という拡張子を見たことがありませんか?
これこそがまさにJS/CSSの最適化です。
通常は、GulpのようなタスクランナーやWebpackのようなモジュールバンドラーを使って最適化をします。
画像ファイルに関しては、リサイズしたりすることによって大幅にファイルサイズを削減することができます。Gulpで言うところのgulp-imagemin
などですね。
ちなみに、.min
っていう拡張子は「Minifyしたぞ!」っていう意思表示のためだけにつけられています。
圧縮
圧縮アルゴリズムの種類としてgzip
, zopfli
, brotli
などがあり、目新しいのはGoogleの圧縮アルゴリズムであるbrotli
です。
圧縮については最適化と違って以下の2種類のルートがあります。
- 予め圧縮したファイルをサーバーに置く
- サーバーで圧縮する
1.予め圧縮したファイルをサーバーに置く
に関しては、上記ののGulpやWebpackなどを使って予め圧縮する方法です。
2. サーバーで圧縮する
に関しては、Apache
のhttpd.conf
にmod_brotli
やmod_deflate
の設定を書き、それぞれモジュールを置いておくだけでサーバー側で圧縮して返却するようになっています。
brotli
に関しては、導入するためにhttpd
をソースから落としてきたりする必要があるようで、導入コストがやや高めとなっています。
ケースバイケースですが、最低限gzip
に対応しておく必要はあると思います。
サーバーで圧縮する場合は、圧縮するだけサーバーに負荷がかかるため、可能な限り予め圧縮しておきましょう。
リクエスト数の最適化
画像をまとめる
いわゆるCSSスプライトという技術で、表示する画像を1つのファイルにまとめる方法。
表示する画像ファイルの分だけリクエストを送るため、それらを1つにまとめることによって1回のリクエストで済ませることができます。
JS/CSSをまとめる
コーディングをする上ではそれぞれ別ファイルで管理していた方が進めやすいですが、別々のファイルとして読み込んでいると、それだけリクエストが多くなるので、なるべくJSとCSSをそれぞれ1つのファイルにまとめた方が良いです。
キャッシュする
httpd.conf
等にmod_expires
の設定を書くことによって、キャッシュのルールを設定することができます。
<ifModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 1 months"
ExpiresByType image/jpeg "access plus 1 months"
</ifModule>
上記の設定によって、HTMLファイルと画像ファイルをそれぞれ1ヶ月間の期限を設定してキャッシュするようになります。
頻繁にリニューアルされるページや更新されるファイルについてはあまりオススメできませんが、ファイル名をバージョニングをすることによって強制的にキャッシュを効かなくする方法もあります。
期間に関しては、GoogleDevelopersやRFCのガイドラインにも書いてある通り、1週間〜1年間の間で設定すると良いです。
また、キャッシュそのものについてはCache-controlヘッダーで設定することができます。
Cache-controlについて、最近追加されたimmutableレスポンスもありますが、現在は対応するブラウザが少ないため、割愛します。
クリティカルレンダリングパスの最適化
HTMLファイルのbody
タグの内容が表示されるまでにブラウザはサーバーにリクエストをめちゃめちゃ投げます。
例えばhead
タグ内でCSSファイルやJSファイルをたくさん読み込んでいると、その分だけ表示されるまでの時間が伸びます。
そういったレンダリングブロックの解決策は、ファーストビューが綺麗に表示される最小限のファイルだけ読み込むことです。
基本的に、JSに関してはbody
の閉じタグ直前に置くことによって解決しますが、CSSに関してはそう簡単には行きません。
単純にCSSの読み込みを遅くした場合、一瞬ですがCSSが適用されていないページが表示されてしまいます。
ですので、Critical Path CSS Generatorのようなサイトで、ファーストビューに使われるCSSのみ抽出し、ファーストビュー用のCSS
と全てのCSS
の2つにCSSファイルを分けます。(あるいはファーストビューのCSSのみ直接インライン化します)
レイアウト計算/スクリプト処理の最適化
ルールセットやCSSセレクタを整理する
Webページを更新していると、使われなくなったCSSが生まれます。そういったCSSの記述を定期的にメンテナンスしていないと、それだけ処理の無駄な時間が増えてしまいます。
また、.class .div .div a
などとCSSセレクタを記述した場合、それだけマッチングの時間がかかってしまいます。
ですので、class
やid
を適切に振り、なるべくマッチングの処理時間を減らした方が良いです。
CSSとJSのアニメーション
基本的にCSSで実現できるアニメーションはJavaScriptでも実現することができます。
ですので、JavaScriptでアニメーションを書いてしまいがちですが、WebWorkerなどを使わない限りJavaScriptはメインスレッドで処理が行われるので、他の処理にも影響を与えてしまいます。
一方で、CSSのアニメーションはコンポジットスレッドで処理されるためメインスレッドに影響を与えることはありません。
可能な限りCSSで実現することによってサクサクのWebページにすることができます。
処理の時間を計測する
ChromeのデベロッパーツールのPerformanceパネルで計測することによってどの処理にどれだけの時間を費やしたか確認することができます。
範囲を指定すると、以下のようにミリ秒単位で処理時間の内訳を表示してくれます。
処理が重い原因を見つける際にはこういったプロファイラを使って地道に見つけて行きましょう。
この作業でほとんどの重い処理は解決できます。
メモリの最適化
メモリに関しても、ChromeのデベロッパーツールのPerformanceパネルで確認することができます。
上記の画像の場合は処理が終わった後に使用量がガクッと下がっていて異常はありませんが、解放されないメモリが増え続けて、ヒープが圧迫される場合は使用量が上がりっ放しになります。
こうなってくると、ガベージコレクションの実行頻度も上がり、負のスパイラルに陥ります。
Performanceパネルでメモリリークが発生するおおよそのタイミングを特定し、Memoryパネルのスナップショットで参照が残ってしまっているオブジェクトなどを特定しましょう。
スピードテスト
Webページの高速化のようなパフォーマンスチューニングにおいて、とても素晴らしい名言があります。
それは、「推測するな、計測せよ」です。
「許可を求めるな、謝罪せよ」に並んで、全日本エンジニアが知るべき大事な考え方選手権の上位に食い込む名言だと思っています。
というわけで、いくつかスピードテストするためのサイトを紹介します。
検証のために有名なサイトで測ってみました。
PageSpeed Insights
有名どころですね。
Googleが提供するページスピードを測るツールです。
点数の他にも、最適化の方法を教えてくれます。
WEBPAGETEST
個人的にはココのサイトが好きです。
割と厳しめに評価してくれて、かつそれぞれの評価基準を段階評価してくれます。
PageSpeed Insightsでブイブイいってた有名なサイトもここでは厳しめに評価されています。
Chrome Developer Tool
いやいや、ページ飛ぶのめんどくさいよって人向けに、Chromeのデベロッパーツールにも搭載されています。
手軽に計測できてUIもシンプルで見やすいので、そこそこオススメです。
最後に
パフォーマンスチューニングはとても奥が深いので他にも色々な方法があります。
CDNやサーバースペックなどに関しては触れませんでしたが、フロント・バック・インフラ全ての人に関わる部分なので、覚えておいて損はしません。
ちなみに、ブラウザの表示までの細かな動きは実際のところ「ブラウザを立ち上げてページが表示されるまで」には何が起きるのかに詳しく書かれています。とても勉強になりました。