HTML
CSS
JavaScript
Web

表示の遅いWebページは何も価値がない

おはこんばんちは、渋谷でお掃除してる人です。

はじめに

自社サービス、ランディングページ、企業ホームページのようなWebサイト(Webページ)において何が一番大切かと聞かれれば、その答えは表示速度です。

ええ、仰りたいことは大いにわかります。もちろんコンテンツも大事です。
ですが、どれだけコンテンツが良くて魅力的な商品だったとしても、表示されなければ意味がありません。

人間という生き物は、Webページが表示されるまでたったの2秒しか待てません。
ウルトラマンでさえ3分間も待てるこのご時世に、たったの2秒です。

その2秒によって、汗水流して作りあげたみなさんのコンテンツが人目につくこともなく、消えていくのです。

というわけで

Webページを高速化する上で大切なことをちょっとだけ紹介します。

この記事を読み終わる頃には、ちょっとだけ高速化に詳しくなって、ちょっとだけPageSpeed Insightsで高得点を取れるようになるかと思います。

早速やって行きましょう。

Webページが表示される仕組み

どうやってWebページが表示されてるの?って思ったまま読んで頂くのも大変申し訳ないので、ちょっとだけ説明します。

Webページが表示されるまでの大まかな流れは

  1. ブラウザからサーバーへファイルを要求(リクエスト)
  2. サーバーがブラウザからの要求に応じてファイルを返却(レスポンス)

たったのこれだけです。(DNSサーバーとかの話は割愛)

上記の流れを超簡単に図にするとこのような感じです。
image-1.png
超簡単ですね。

また、リクエストから表示されるまでの流れをもう少しだけ加えると、このような感じです。
Untitled Diagram-Page-4.png

上記のようにして、僕たち私たちはパソコンのブラウザ上でWebページを見ることができています。
実際は表示するまでに何度もリクエストを飛ばしているわけですが、その辺はマサカリ飛ばさないでほしいです

Webページの高速化

では、Webページをどのように高速化してくのかという話に移りましょう。
高速化していくためには、最適化をする必要があります。
英語でいうとOptimizationです。テストに出るので覚えましょう。

まさに最適化をすることで、Webページは高速で表示され、閲覧する人全てがハッピーになります。
そこで、今回はネットワーク処理描画処理の2つに分けて見て行きます。

最適化のポイント

具体的な最適化の方法を紹介する前に、ネットワーク処理/描画処理とは?っていう心の声が聞こえてしまったのでちょっとだけ補足しておきます。

ネットワーク処理

今回は便宜上、描画処理以外で色々やってることっていうことにしておきます。
深くは考えないようにしてください。私は深く考えられないタイプの人間なので。

描画処理

Webサーバーにリクエストを投げて、受け取ったレスポンスからWebページを描画する処理です。
Webページが表示されるまでに、以下の工程を踏んでいます。

  1. DOM/CSSOMツリーの構築
  2. DOM/CSSOMツリーからレンダーツリーの構築
  3. レイアウトの決定
  4. ペイント(描画)

上記のようにレンダリング処理などを行い、そして、JavaScriptによってDOMツリーにあーだこーだスクリプト処理が行われます。

 最適化の方法

前置きが長くなってしまいましたが本題に行きましょう。
image-3.png

ネットワーク処理の最適化の方法は以下の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. 予め圧縮したファイルをサーバーに置く
  2. サーバーで圧縮する

1.予め圧縮したファイルをサーバーに置くに関しては、上記ののGulpやWebpackなどを使って予め圧縮する方法です。
2. サーバーで圧縮するに関しては、Apachehttpd.confmod_brotlimod_deflateの設定を書き、それぞれモジュールを置いておくだけでサーバー側で圧縮して返却するようになっています。

brotliに関しては、導入するためにhttpdをソースから落としてきたりする必要があるようで、導入コストがやや高めとなっています。
ケースバイケースですが、最低限gzipに対応しておく必要はあると思います。
サーバーで圧縮する場合は、圧縮するだけサーバーに負荷がかかるため、可能な限り予め圧縮しておきましょう。

リクエスト数の最適化

画像をまとめる

いわゆるCSSスプライトという技術で、表示する画像を1つのファイルにまとめる方法。
表示する画像ファイルの分だけリクエストを送るため、それらを1つにまとめることによって1回のリクエストで済ませることができます。

JS/CSSをまとめる

コーディングをする上ではそれぞれ別ファイルで管理していた方が進めやすいですが、別々のファイルとして読み込んでいると、それだけリクエストが多くなるので、なるべくJSとCSSをそれぞれ1つのファイルにまとめた方が良いです。

キャッシュする

httpd.conf等にmod_expiresの設定を書くことによって、キャッシュのルールを設定することができます。

httpd.conf
<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のみ直接インライン化します)

image-2.png

レイアウト計算/スクリプト処理の最適化

ルールセットやCSSセレクタを整理する

Webページを更新していると、使われなくなったCSSが生まれます。そういったCSSの記述を定期的にメンテナンスしていないと、それだけ処理の無駄な時間が増えてしまいます。

また、.class .div .div aなどとCSSセレクタを記述した場合、それだけマッチングの時間がかかってしまいます。
ですので、classidを適切に振り、なるべくマッチングの処理時間を減らした方が良いです。

CSSとJSのアニメーション

基本的にCSSで実現できるアニメーションはJavaScriptでも実現することができます。

ですので、JavaScriptでアニメーションを書いてしまいがちですが、WebWorkerなどを使わない限りJavaScriptはメインスレッドで処理が行われるので、他の処理にも影響を与えてしまいます。
一方で、CSSのアニメーションはコンポジットスレッドで処理されるためメインスレッドに影響を与えることはありません。

可能な限りCSSで実現することによってサクサクのWebページにすることができます。

処理の時間を計測する

ChromeのデベロッパーツールのPerformanceパネルで計測することによってどの処理にどれだけの時間を費やしたか確認することができます。
スクリーンショット 2018-09-16 16.34.15.png

範囲を指定すると、以下のようにミリ秒単位で処理時間の内訳を表示してくれます。
スクリーンショット 2018-09-16 16.36.15.png

処理が重い原因を見つける際にはこういったプロファイラを使って地道に見つけて行きましょう。
この作業でほとんどの重い処理は解決できます。

メモリの最適化

メモリに関しても、ChromeのデベロッパーツールのPerformanceパネルで確認することができます。

以下の画像の赤の枠内がメモリ使用量の推移を表しています。
スクリーンショット 2018-09-16 16.53.33.png

上記の画像の場合は処理が終わった後に使用量がガクッと下がっていて異常はありませんが、解放されないメモリが増え続けて、ヒープが圧迫される場合は使用量が上がりっ放しになります。
こうなってくると、ガベージコレクションの実行頻度も上がり、負のスパイラルに陥ります。

Performanceパネルでメモリリークが発生するおおよそのタイミングを特定し、Memoryパネルのスナップショットで参照が残ってしまっているオブジェクトなどを特定しましょう。

スピードテスト

Webページの高速化のようなパフォーマンスチューニングにおいて、とても素晴らしい名言があります。

それは、「推測するな、計測せよ」です。

「許可を求めるな、謝罪せよ」に並んで、全日本エンジニアが知るべき大事な考え方選手権の上位に食い込む名言だと思っています。

というわけで、いくつかスピードテストするためのサイトを紹介します。
検証のために有名なサイトで測ってみました。

PageSpeed Insights

有名どころですね。
Googleが提供するページスピードを測るツールです。

スクリーンショット 2018-09-16 17.15.30.png

点数の他にも、最適化の方法を教えてくれます。

WEBPAGETEST

個人的にはココのサイトが好きです。
割と厳しめに評価してくれて、かつそれぞれの評価基準を段階評価してくれます。

スクリーンショット 2018-09-16 17.16.00.png

PageSpeed Insightsでブイブイいってた有名なサイトもここでは厳しめに評価されています。

Chrome Developer Tool

いやいや、ページ飛ぶのめんどくさいよって人向けに、Chromeのデベロッパーツールにも搭載されています。

スクリーンショット 2018-09-16 17.17.16.png

手軽に計測できてUIもシンプルで見やすいので、そこそこオススメです。

最後に

パフォーマンスチューニングはとても奥が深いので他にも色々な方法があります。
CDNやサーバースペックなどに関しては触れませんでしたが、フロント・バック・インフラ全ての人に関わる部分なので、覚えておいて損はしません。

ちなみに、ブラウザの表示までの細かな動きは実際のところ「ブラウザを立ち上げてページが表示されるまで」には何が起きるのかに詳しく書かれています。とても勉強になりました。