Smashing Magazineのパフォーマンス改善ケースが凄まじい件

Improving Smashing Magazine’s Performance

Frontrend Advent Calendar 2014 - Qiitaの24日目。たぶん。知らんけど。

The Style & Class Conference 2014

ちょっと前になるが12/13にバンクーバーで開催されたThe Style & Class Conference 2014に参加してきた。前日にSmashing Conferenceが、ウィスラーというバンクーバーから比較的近い所で開催されていて、本当はそっちに行きたかったんだけど高額なため、地元コミュニティのほうにだけ参加した。ウィスラーの方の記事は@ygoto3が書いてたっぽい。

Smashing Conferenceで登壇していたJohn Allsopp氏やVal Head氏もこのカンファレンスで登壇するということで、『なんだ、ウィスラーのついでかよー』と思い全然期待してなかったのだが、行ってみたらカンファレンス全体の構成などすごく考えられていて、とても素晴らしいカンファレンスだった。

そんなわけで、今回はその中で最も気に入ったVitaly Friedman氏のセッションを紹介したいと思う。

Improving Smashing Magazine’s Performance

Vitaly Friedman

講演内容と同じ内容と思われる記事がすでに9月に上がっているみたい。バックエンドからフロントエンドまでいろいろなことやってみるみたいで、めちゃめちゃ長文なので時間あるときにでも読んでおくとよい。その中でも僕が気に入ったのがCritical CSSの対応をしていたことだ。

Critical Rendering Path

Critical Rendering Pathとは、HTML/CSS/JSなどのバイトの取得からピクセルとしてレンダリングする必須処理までの間の段階のことを言い、Critical CSSとはページの最初のレンダリングをブロックする可能性のあるCSSのことを言う。

Critical Rendering Path

なぜCSSがレンダリングをブロックするのかというと、上図の通り、レンダリングを完成するにあたってブラウザはDOMとCSSOM(CSSオブジェクトモデル)が必要なわけでして、スタイルシートがダウンロードされない限りレンダリングが開始されないわけだ。

  1. HTML マークアップを処理して DOM ツリーを作成
  2. CSS マークアップを処理して CSSOM ツリーを作成
  3. DOM と CSSOM を組み合わせてレンダーツリーを作成
  4. レンダーツリーでレイアウトを実行して各ノードの形状を計算
  5. 各ノードを画面にペインティング

少なくともブラウザに何かが描画されるまでには上記のような流れをふまないといけない。

そうゆうわけで、是が非でも速く描画するために、Critical Rendering Pathの最適化をしようとすると以下のことに気をつけなければならない。

  • クリティカル リソース数の最小化
  • クリティカル バイト数の最小化
  • クリティカル パス長の最小化

ことCSSだけに関して言えば、リソース数の最小化はスタイルシートを何個も読み込まず、1個にまとめればよいし、バイト数の最小化はCSSOgzipをかけてやればよい。

まぁそれらはそんなに難しいことではないので、すぐにでも対応できると思われる。問題なのはクリティカル パス長(音的にリヴァイ兵長みたいな感じなので以後Critical Path Lengthと表記する)の最適化だ。

基本的には外部スタイルシートとして読み込むファイルを1個にまとめれば、HTML読み込んで、そのCSSを読み込むのがCritical Path Lengthの最短じゃねーのかと思うが、それではGoogle様が認めてくれない。

試しに、PageSpeed Insightsで僕のプロフィール(単純な静的ページで外部CSSファイル1個)ページ:t32k.meを計測してみると、『スクロールせずに見えるコンテンツのレンダリングをブロックしている JavaScript/CSS を排除する』なことを言われモバイル評価で89点といった結果が返ってきた。

PageSpeed Insights:Before

で、対処法としてCSSの配信を最適化しなさいと言われる。こっちの説明よりWeb Fundamentalsの説明のほうが分かりやすいのでこっちを引用。

インライン レンダリング ブロック CSS
クリティカル CSS は、HTML ドキュメント内で直接インライン化することをおすすめします。これにより、クリティカル パスの追加ラウンドトリップが削減され、適切に設定できれば、HTML が唯一のブロック リソースの場合に「1 ラウンドトリップ」のクリティカル パス長が実現できます。
PageSpeed Rules and Recommendations

つまり、外部スタイルシートを読み込んでいては1.) HTMLを読み込む、2.) 外部CSSファイルを読み込むので、最低でも2ラウンドトリップ(往復)しないといけない。ゆえに描画が遅くなるのでHTML内にインラインで記述しなよっと仰せられておる。

でも、だからといって全部CSSをインライン化しちゃうとHTMLが膨れ上がっちゃう。TCPスロースタートのせいで1回目のレスポンスで送信できるサイズは14KBなので、オーバーしちゃう。この辺りは以前にHTTPリクエストを減らすためにシリーズで記事を書いたので参照してほしい。

または、Ilya Grigorik大先生の本を読んでおいた方がいい。

だもんで、必要なCSSだけインライン化しましょうよってことになる。それ(Above the Fold)に必要なCSS、つまりCritical CSSを検出するのがnpmモジュールのCriticalCSSだ。

ATFの例

簡単に説明すれば上図のようにファーストビュー(Above the Fold)だけに使うCSSを抽出してくれる。

ちなみにGruntプラグインで利用できるので、これを使って僕のプロフィールページ:t32k.meを改善してみる。

grunt.initConfig({
  criticalcss: {
    custom_options: {
      options: {
        url: "http://localhost:8000",
        width: 1024,
        height: 768,
        outputfile: "_templates/includes/critical.css",
        filename: "skeleton.min.css"
      }
    }
  }
});

こんな感じでGruntfileの方は記述する。重要なのはwidht/heightで、ここで自分の好きなAbove the Foldを定義する。で出力したCSSをテンプレート側で読み込む。

今回は、1.) CriticalCSSでクリティカルCSSを生成、2.) CSSOでミニファイ、3.) Jadeでコンパイル読み込むという流れ。詳しくはGitHubにあげてあるので参照してね。

で、Full CSSの方は、後から非同期で読み込む。こうしないとレンダリングをブロックするので。あ、ちなみにほぼSkeleton.cssをそのまま使ってる(^_^;)

<script>
/*!
  loadCSS: load a CSS file asynchronously.
  [c]2014 @scottjehl, Filament Group, Inc.
  Licensed MIT
*/
function loadCSS(href, before, media) {
  var ss = window.document.createElement('link');
  var ref = before || window.document.getElementsByTagName('script')[0];
  var sheets = window.document.styleSheets;
  ss.rel = 'stylesheet';
  ss.href = href;
  ss.media = 'only x';
  ref.parentNode.insertBefore(ss, ref);
  return ss;
}
loadCSS('/skeleton.min.css');
</script>
<noscript>
  <link rel="stylesheet" href="/skeleton.min.css">
</noscript>

で、Critical CSSに対応した結果をPageSpeedにかけてみると、

PageSpeed Insights:After

めでたく、先ほどの指摘はクリアできました。わーい、97点٩(๑❛ᴗ❛๑)۶

ちなみに『ブラウザのキャッシュを活用する』はGitHub Pagesなので僕からHTTP Headerを変更できないのでほっとく。

Speed Index <= 1000

なにをもって速いとするのか?というのは重要な問題だ。PageSpeed Insightのスコアも一種の指標となるだろうが、もう少し細かく検証したい。(事実、PageSpeedのスコアは90点くらいまでなら簡単に取れる)

最近は読み込み時間が体感速度を表しているように思えない。各種SNSボタンのJSが大量に読み込まれるが、それらは非同期で読み込まれるために実際の読み込み時間と体感速度には大きな乖離が見られるし、何千pxという長大なページで2,3スクロールしないと見えないような画像が読み込み時間にカウントされるのはどうだろう。はたまたdomContentLoadedだったらどうだろうか、うーん、あんましフロント関係なくね?

そんなこんなで現時点で一番有用な指標と個人的に考えているのが、WebPagetestで計測できるSpeed Indexだ。Speed Indexに関しても以前記事を書いた。

So how fast is fast enough? A Speed Index of under 1000. And for professionals that get there, they should shoot for delivering the critical-path view (above the fold) in the first 14Kb of the page. — Paul Irish

Smashing Magazineの講演でも触れられていたが、やはりどれだけ速ければいいのかという問いに対して、Web業界のベネディクト・カンバーバッチであるPaul Irish氏が言及していたようにSpeed Indexが1000以下になるのが望ましい。これは去年も来日してた時に言っていたのでGoogle様はそれを目標にしているのだろう。そうゆうわけでのクリティカル・パスの最適化である。

Smashing Magazineではgrunt-perfbudgetを使って、定期的にWebPagetestを回していたらしい(CLIからWPTを動かすにはAPI Keyが必要なので個別に作者に連絡しなければならない)。

今回の改善によるSpeed Indexの変化だけど、GitHub Pagesでカスタムドメインしているため、どうしても最初にリダイレクトが入ってしまうせいで、改善前後のSpeed Indexは微減(2097 -> 1940)だが、Start Renderは1.8秒から1.6秒と確実に速くなっている。

Smashing Magazineのケースでも一連の改善の結果、1000近くにまで削減することができたそうだ。その結果、『SmashingMagはサンパウロからEDGE回線で読むことができるただ一つのサイトだ』と講演の最後にブラジルの読者からのツイートを誇らしげに紹介していたVitaly Friedman氏の笑顔が忘れられない。

まとめ

そうゆうわけで、Smashing Magazineの改善ケースでやってること自体は特に目新しい物はないが、ひとつひとつのことを丁寧にしっかりやってる点が素晴らしいと思う。しかもSmashing Magazineのような長年運用している大規模かつ複雑なサイトでCritical CSSの対応などは相当めんどくさかったに違いない(もっと詳しく聞きたかった)。今回の簡単な静的ページであるプロフィールページの改善もめんどくさかったし。

結局、山ほどあるパフォーマンス改善策に優先度を決め、ゴールを決め、フロントとバックエンドをまとめ、戦略をもってパフォーマンス改善できる人なんてそうそういないよね?てか、対象となる知識大杉、てか、Vitaly Friedman氏ハンパなくね?って思った。

Smashing Magazineにはスーパーマンがいたけど、個人的にはもっと他のケースも知りたいというか、泥臭いのに共感したいと思っている。だって世の中そんなうまくいかないし、テキスト主体で画像少なめのページでこれ速いだろうって言っても意味ねーし、世の中もっとゴテゴテしてるし複雑だ。この辺は緑の顔の緑の会社の人をチェックしていれば、いつか闇がにじみだしてくるのではと期待している。

僕の来年の目標はやっぱり、Speed Indexの知名度が低いのもWebPagetest.orgの見た目がうさくさいのが原因だと思っているので、リニューアルデザインをプルリクしてあげたいと思う。たぶん。知らんけど。