レンダリングを妨げるリソースの除外/CSSを非同期で読み込む

PageSpeed Insights や Chrome の拡張機能 Lighthouse を使ってページをテストすると「レンダリングを妨げるリソースの除外」という項目が表示されることがあります。

この項目が表示される原因の1つは CSS ファイルの読み込みなどのレンダリングブロックによる表示の遅延です。

以下はレンダリングをブロックする CSS ファイルの読み込みを最適化してページの表示速度を改善させる方法と WordPress で CSS を非同期で読み込む方法の覚書です。

作成日:2020年4月14日

レンダリングのブロック

以下はあるサイトのページを PageSpeed Insights でテストした結果の一部抜粋です(同様のテストは Chrome の拡張機能 Lighthouse でも可能です)。

「改善できる項目」が複数表示されていて、その中に「レンダリングを妨げるリソースの除外」という項目があり、展開すると以下のように書かれています。

ページの First Paint をリソースがブロックしています。重要な JavaScript や CSS はインラインで配信し、それ以外の JavaScript やスタイルはすべて遅らせることをご検討ください。

First Paint とは、ファーストビュー、つまり初回に表示されるスクロールせずに見えるコンテンツの範囲のことです。レンダリングを妨げるリソースは CSS や JavaScript の読み込みのことになります。

また、レンダリングとはブラウザが Web ページを表示するための描画処理のことで、レンダリングのブロックとはその描画処理を妨げることです。

通常の CSS の読み込みは同期なので、ブラウザが CSS ファイルを読み込みその後解析が完了するまでは描画処理をしないので、ページの表示に遅延が発生します(レンダリングをブロックします)。

レンダリングブロックを回避する方法の1つは CSS の配信を最適化します。具体的にはファーストビューの表示に必要な CSS のみを <head> 内にインラインで記述して、他の CSS は非同期で読み込みます。

それによりファーストビューの表示を速くすることができ、ページ表示の体感速度を改善することができます(ページ全体の読み込み速度は変わりません)。

参考情報:

以下は前述のサイトのページで CSS の最適化を実施した後のテスト結果です。「レンダリングを妨げるリソースの除外」という項目が消えて、First Paint の表示スピードが改善されました。

※上記は非常に大きなサイズの CSS を読み込んでいるページの例です。実際の効果はページの構造や読み込んでいる CSS のサイズなどにより異なります。

CSS 配信の最適化

以下は PageSpeed Tools Insights の CSS の配信を最適化する からの引用です。

大きな CSS ファイルの場合は、スクロールせずに見える範囲のコンテンツの表示に必要な CSS を特定してインライン化し、残りのスタイルの読み込みはスクロールせずに見える範囲のコンテンツの表示後まで遅らせる必要があります。

大きな CSS ファイルの場合、スクロールせずに見える範囲のコンテンツ(above-the-fold)に必要な CSS(Critical CSS)を特定する必要があります。

Critical Rendering Path(クリティカルレンダリングパス)とは、ブラウザがページの最初の描画のために実行する必要がある処理のことですが、Critical CSS はスクロールせずに見える範囲のコンテンツに必要な CSS のことです。

また、<link rel="stylesheet"> による CSS の読み込みは同期なのでレンダリングをブロックするため、Critical CSS をインラインで記述して、その他の CSS を非同期で読み込む(読み込みを表示の後まで遅らせる)ようにします。

以下が CSS 配信の最適化の大まかな手順です。

  1. スクロールせずに見える範囲のコンテンツに必要な CSS(Critical CSS)を抽出
  2. 取得した Critical CSS を <head> 内の <style> 要素に記述(インライン化)
  3. その他の CSS を非同期で読み込むように記述を変更

参考情報:

Critical CSS の抽出とインライン化

対象ページのスクロールせずに見える範囲のコンテンツ(above-the-fold)に必要な CSS(Critical CSS)を特定して抽出する必要があります。

Critical CSS を取得(抽出)するには、以下のようなオンラインツールを利用することができます。

また、以下のようなツールもあります。

以下では簡単に使えるオンラインツール Critical Path CSS Generator(jonassebastianohlsson.com) を使用しています。

  1. 対象ページの URL を入力します。
  2. そのページの全ての CSS を貼り付けます。
  3. 「Create Critical Path CSS」をクリックすると、Critical CSS が生成されるのでコピーします。

CSS の貼り付けは、複数の CSS を読み込んでいる場合、全てまとめて貼り付けても良いですし、1つずつ個別に CSS を貼り付けて同じ処理を繰り返すこともできます。

生成される Critical CSS はコメント部分や改行は削除され、ミニファイ化(圧縮)されます。既にミニファイ化したものを貼り付けても問題ありません。

また、@charset の指定は外部スタイルシートファイル内でのみ使用可能なので除外しておきます。

注意点

背景画像などを相対パスで参照している場合コードの修正が必要になるかもしれません。背景画像やフォントが正しく表示されない場合は、相対パスを絶対パスに書き換えてから再度試してみるのが良いようです。

コピーした CSS を <head> 内の <style> 要素に貼り付けてインライン化します。

<head>
<meta charset="utf-8">
・・・中略・・・
<style> /* 生成された Critical CSS を貼り付け */ </style>
<link rel="stylesheet" href="../style.min.css">
<link rel="stylesheet" href="../custom.min.css">

複数の CSS を個別に生成してそれぞれ <style> 要素で読み込むと管理しやすいかもしれません。

例えば、style.css と custom.css の2つの CSS がある場合は、以下のように個別に生成してそれぞれ <style> 要素に貼り付けることもできます。

<head>
<meta charset="utf-8">
・・・中略・・・
<style> /* 生成された style.css の Critical CSS を貼り付け */ </style>
<style> /* 生成された custom.css の Critical CSS を貼り付け */ </style>
<link rel="stylesheet" href="../style.min.css">
<link rel="stylesheet" href="../custom.min.css">

CSS を非同期にロード

CSS を非同期に読み込むには以下のような方法があります。

  • <link rel="preload"> で CSS を読み込み loadCSS などの polyfill(ポリフィル)を使う
  • <link rel="stylesheet" media="print" onload="this.media='all'"> で CSS を読み込む

1つめの方法は <link> 要素の rel 属性に preload を指定することで、レンダリングブロックを回避して非同期に CSS を読み込み、onload 属性などを使ってスタイルを適用します。

但し、現時点では preload に対応していないブラウザ(IE や Firefox など)があるため、loadCSS などの polyfill を使用する必要があります。(rel="preload"

2つめの方法は media 属性に print を指定して、onload 属性を使ってロードが完了したら media 属性を all に変更するというもので The Simplest Way to Load CSS Asynchronously に解説があります。

この方法は media 属性に print を指定するとブラウザがレンダリングブロックすることなく非同期に CSS を読み込むことを利用して、ロードが完了したら(onload 属性を使って)media 属性を all に変更することでスタイルを適用しています。

IE や Firefox などでも機能するので、loadCSS などの polyfill を使用する必要がありません。

media 属性に print を指定

以下は media 属性に print を指定して非同期に CSS を読み込む方法です。

メディアタイプの print は印刷の際に適用するスタイルシートの指定なので、CSS がロードされた後で screen(または all)に適用されるようにする必要があります。

そこで、文書の読み込み(ロード)が完了したというイベントに対しての処理を指定する onload 属性を使ってメディアタイプを all に変更します。

以下は style.min.css と custom.min.css という2つの CSS を非同期に読み込む例です。

<link rel="stylesheet" href="../style.min.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="../custom.min.css" media="print" onload="this.media='all'">

Critical CSS のインライン化も含めるとコードは以下のようになります。

<head>
<meta charset="utf-8">
・・・中略・・・
<style> /* 生成された Critical CSS を貼り付け */ </style>
<link rel="stylesheet" href="../style.min.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="../custom.min.css" media="print" onload="this.media='all'">

onload 属性で JavaScript を使っているので、JavaScript がオフの場合を考慮して <noscript> タグで通常のスタイルシートの読み込みを記述します。

<head>
<meta charset="utf-8">
・・・中略・・・
<style> /* 生成された Critical CSS を貼り付け */ </style>
<link rel="stylesheet" href="../style.min.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="../custom.min.css" media="print" onload="this.media='all'">
<noscript> <!-- フォールバックとしての通常のスタイルシートの読み込み  -->
<link rel="stylesheet" href="../style.min.css">
<link rel="stylesheet" href="../custom.min.css">
</noscript>

以下のように記述しても同じです。

<head>
<meta charset="utf-8">
・・・中略・・・
<style> /* 生成された Critical CSS を貼り付け */ </style>
<link rel="stylesheet" href="../style.min.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="../style.min.css"></noscript>
<link rel="stylesheet" href="../custom.min.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="../custom.min.css"></noscript>

ブラウザで表示を確認し、 PageSpeed Insights などでテストして問題がなければ完了です。

WordPress での CSS 非同期読み込み

WordPress で CSS を読み込む方法は色々とありますが、以下は wp_enqueue_style を使って読み込んだ CSS を style_loader_tag というフィルターフックを使って変更する例です。

style_loader_tag は読み込みキューに入っている link 要素の HTML をカスタマイズすることができます。

以下はブログトップページ(home)の場合に、登録されている link 要素の media 属性を print に変更し、onload="this.media='all'" を追加しています。

ヒアドキュメントを使って link 要素の HTML を作成しています。EOT(11行目)の前後にスペースなどが入っているとエラーになるので注意が必要です。

wp_enqueue_style を使って CSS を読み込ませている場合、WordPress によってハンドル名($handle)を使った id 属性が付与されるので、id="{$handle}-css" としています。

また、元の link 要素の HTML を <noscript> で囲んでフォールバックとして出力するようにしています。

functions.php
//home の場合はCSSを非同期で読み込む
function load_css_async_top($html, $handle, $href, $media) {
  //ホームの場合のみカスタマイズ
  if(is_home()) {
    //元の link 要素の HTML(改行が含まれているようなので前後の空白文字を削除)
    $default_html = trim($html);
    //HTML を変更
    $html = <<<EOT
<link rel="stylesheet" id="{$handle}-css" href="$href" media="print" onload="this.media='all'">
<noscript>{$default_html}</noscript>\n
EOT;
  }
  return $html;
}
add_filter( 'style_loader_tag', 'load_css_async_top', 10, 4 );

例えば、Gutenberg ブロック用のデフォルトスタイルは通常は以下のように出力されます。

<link rel='stylesheet' id='wp-block-library-css'  href='https://example.com/wp-includes/css/dist/block-library/style.min.css?ver=5.4' media='all' />

上記のフックを記述すると以下のように出力されます。

<link rel="stylesheet" id="wp-block-library-css" href="https://example.com/wp-includes/css/dist/block-library/style.min.css?ver=5.4" media="print" onload="this.media='all'">
<noscript><link rel='stylesheet' id='wp-block-library-css'  href='https://example.com/wp-includes/css/dist/block-library/style.min.css?ver=5.4' media='all' /></noscript>

Critical CSS の出力もいろいろな方法があると思いますが、以下は抽出した Critical CSS を critical-home.css というファイルに保存して home の場合に wp_head アクションフックで <head> 内に出力する例です。 critical-home.css はテーマ内の inc というフォルダに配置しています。

出力される位置は priority(以下の場合は 3)で調整することができます。また、include_once の代わりに echo file_get_contents() を使うこともできますが、それが良いのかはわかりません。

functions.php
//home の場合に Critical CSS を挿入
function add_critical_css_top(){
  if(is_home()) {
?>
<style><?php include_once get_template_directory() . '/inc/critical-home.css'; ?></style>
<?php
  }
}
add_action( 'wp_head', 'add_critical_css_top', 3 ); 

以下は出力例です。

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>お知らせ</title>
<link rel='dns-prefetch' href='//ajax.googleapis.com' />
<link rel='dns-prefetch' href='//fonts.googleapis.com' />
<style>
/* critical-home.css に保存した Critical CSS  */
</style>
<link rel="stylesheet" id="wp-block-library-css" href="https://example.com/wp-includes/css/dist/block-library/style.min.css?ver=5.4" media="print" onload="this.media='all'">
<noscript><link rel='stylesheet' id='wp-block-library-css'  href='https://example.com/wp-includes/css/dist/block-library/style.min.css?ver=5.4' media='all' /></noscript>
・・・以下省略・・・

※ 上記を試したサイトでは、PageSpeed Insights でテストしたところ、First Contentful Paint や First Meaningful Paint の値は改善されスコアも良くなりましたが、体感速度としては変わらない気がしました。特に初回の読み込みは時間がかかるように思えたので元に戻しました。

rel="preload"

前述の media 属性に print を指定する方法の方がシンプルで polyfill も使う必要がなく個人的にはおすすめですが、以下は <link> 要素の rel 属性に preload を指定して CSS を非同期に読み込む方法です。

rel 属性に preload を指定し、as 属性へリソースの種類(style)を指定します。

<link rel="preload" href="../style.min.css" as="style">

rel="preload" を指定した場合、ファイルは非同期で読み込まれますが、スタイルは適用されないので onload 属性に this.rel='stylesheet' を指定して読み込みが完了したらスタイルを適用するようにします。

また、一部のブラウザは rel 属性を切り替えたときに再度 onload ハンドラを呼び出す可能性があるので、一度使用したら null にしています(this.onload=null)。

<link rel="preload" href="../style.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

更に、preload に対応していないブラウザでも機能するように、<script>タグを使って polyfill(この例では loadCSS)の記述を追加します。

以下に掲載されている loadCSS のコード(cssrelpreload.js)をコピーします。

https://github.com/filamentgroup/loadCSS/blob/master/src/cssrelpreload.js

できれば、コピーしたコードをオンラインツール(Online JavaScript/CSS/HTML Compressor)などを使ってミニファイ化(圧縮)します。

<script>〜</script> にコードを貼り付けます。

<script> 
//コピーしたコード(cssrelpreload.js)をミニファイ化してペースト
/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};i... 以下省略
</script>

フォールバックとして(JavaScript がオフの場合を考慮して)<noscript> タグで通常のスタイルシートの読み込みを記述します。

<link rel="preload" href="../style.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- noscript での通常のスタイルシートの読み込み -->
<noscript><link rel="stylesheet" href="../style.min.css"></noscript>

Critical CSS のインライン化も含めるとコードは以下のようになります。

以下は style.min.css と custom.min.css という2つの CSS を非同期に読み込む場合の例です。

<head>
<meta charset="utf-8">
・・・中略・・・
<style> /* 生成された Critical CSS を貼り付け */ </style>
<link rel="preload" href="../style.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="../style.min.css"></noscript>
<link rel="preload" href="../custom.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="../custom.min.css"></noscript>
<script>
/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ //cssrelpreload.js
!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};i... 以下省略
</script>

参考情報: