プラグインなしで自分で内部・外部リンクのブログカードを表示できるようにしているのですが、コードの書き方がいけてなくてサイト表示に時間がかかっていた(Google ChromeのデベロッパーツールでTTFB(Times to First Byte: 注1)が2秒以上かかっていた)ので、改善できるよう頑張った記録です。

具体的には、ブログカード用のオリジナル関数を読み込むタイミングを変えたり、外部リンク用の画像にキャッシュを設定してみたりした結果、表示速度が1秒以上早くなりました。

現在はWordPressではなく静的サイトジェネレーターの「Hugo」を使っています。そのため、ブログカードへの変換機能は使っていませんが、参考までに情報を残しております。

目次

ブログカードの生成方法

WordPress固有のショートコードやURLの直書きではなく、後から見返したときでもわかりやすいよう、独自に設定したクラス名で囲った<span>タグがブログカードに変換されるようにしています。

bcard ilink(内部リンク用)またはelink(外部リンク用)とclassが設定された<span>が本文中にあったら

1
2
//投稿画面でこのように書いておく(Classic Editorを使っていました)
<span class="bcard ilink"><a href="/make-the-original-blog-card/" title="【WP】プラグインなしで内部リンクのブログカードを実装する方法 - 日なたの縁台" target="_blank" rel="noopener noreferrer">【WP】プラグインなしで内部リンクのブログカードを実装する方法</a></span>

これまでは、記事が表示されるタイミングでブログカードに変換されるようにしていたのですが、この処理用にfunctions.phpに書き加えた部分の処理に時間がかかり、ページ表示がだいぶ遅くなっていました。

試しに他のテーマに変えてみたところページの読み込み速度に問題はなかったので(TTFB 400〜600msくらい)、サーバーではなく自分の設定が原因とわかりました。
(自作テーマを使用中: WordPressのテーマを新しくしました

ちなみに、レンタルサーバーは「エックスサーバー」のX10プラン。独自ドメインもセットで無料取得のキャンペーン中に契約しました。
月額900円(税抜)から、高速・多機能・高安定レンタルサーバー『エックスサーバー』

読み込み速度向上のための取った方策

(1)ブログカード用の関数を読み込むタイミングを変更

functions.phpに書かれている内容は、ページを読み込むためにサーバー上のデータベースとやりとりする前のタイミングですべて処理・実行されます。

しかしこれまではその点を把握しておらず、functions.phpのド頭でブログカード用の関数(blog-card.php)を読み込ませてしまっていました。

1
2
3
//以前のfunctions.phpでの記述
//データベースとやりとりされる前にblog-card.php内の記述がすべて処理される書き方になっていた
include get_template_directory(). '/lib/blog-card.php'; //libフォルダ内のblogcard.phpにブログカード生成の関数をまとめていた

さらに、ブログカード生成用の関数は個別記事でしか必要ないのですが、全てのページで(一覧表示なども含め)読み込まれるようになっていたので、読み込みのタイミングと設定を変更しました。

1
2
3
4
5
6
7
8
9
//修正したfunctions.phpでの記述
//個別記事でのみblog-card.phpを読み込む
//@link https://wordpress.stackexchange.com/questions/249931/how-to-load-file-only-in-single-post-page-through-function-php
add_action( 'wp', 'cb_include_blog_card' );
function cb_include_blog_card() {
    if( is_single() || is_page() ) {
        require get_template_directory(). '/lib/blog-card.php';     //ブログカード関係の関数
    }
}

ここで一回はまったのが、functions.phpはページの読み込みが始まる前にすべて処理されるプログラムなので、この段階ではis_single()などの判定が使えないという点です。

そこで@link先の情報を参考に、add_action()フックを利用して、読み込まれたページが投稿または固定記事の場合に限ってblog-card.php部分が実行されるようにしました。

blog-card.phpの中身は、続く(2)、(3)に出てくるcb_fetch_ogp_image_file()cb_original_blog_card()関数です。

これで、TTFBが600ms秒くらい短くなりました。

(2)外部リンクのOGP画像の拡張子判定を最小限にする

つづいて、外部リンク用のブログカードに使うアイキャッチ画像を取得するための関数(cb_fetch_ogp_image_file())を手直ししました。 手直しした関数はopengraph.phpというオープンソースを利用して外部サイトのOGP画像へのURLが取得できていたら、そのURLから画像データをDLして自分のサーバーに保存するという処理を行うものです。

▼ リンク先に設定されているOGP情報を取得するためのコードopengraph.php
GitHub - scottmac/opengraph: Helper class for accessing the OpenGraph Protocol

当初は、過去記事に書いた通り、OGP画像のバイナリデータから拡張子判定を行っていたのですが、元のファイルに拡張子が付いていた場合はそれをそのまま採用することにしました。(そもそもOGP画像のURLに拡張子がついていないのは、githubくらい!?)

改良したコードでは、WordPressに移行した当初使わせていただいていたテーマ「Simplicity 2」の作者yhiraさんがあらたに公開されているテーマ「Cocoon」のコードを一部参考にさせていただきました。
GitHub - yhira/cocoon: 100%GPLの無料Wordpressテーマです。

一部抜粋にしてしまうとわかりにくくなってしまうので、こちらもまた全部のせします。

cb_fetch_ogp_image_file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* 外部サイトのOGP:imageに設定されている画像を、自分のサーバーの任意のディレクトリに保存する
* @param   string   image_url (OGP:imageのURL)
* @return  string   path(自サーバーでのファイルパス)
**/
if (!function_exists('cb_fetch_ogp_image_file')):
    function cb_fetch_ogp_image_file ( $image_url ) {
    //WP_Filesystemの使用
    require_once(ABSPATH . 'wp-admin/includes/file.php');
    require_once(ABSPATH . 'wp-admin/includes/image.php');
    //require_once(ABSPATH . 'wp-admin/includes/media.php');

    // サーバーの画像を保存するディレクトリを設定
    $dir = ABSPATH . 'wp-content/uploads/elink-img/';

    $image_name = hash('md5', $image_url, FALSE); // ファイルURLからファイル名を決める
    $image_lists =  glob($dir .'*', GLOB_NOSORT); // $dirでのファイルのリストを取得

    $file_existance = preg_grep("/$image_name/", $image_lists); //ファイルの有無を確認

if (!$file_existance): //ファイルがなかったら、画像を取得・保存する

        if ( WP_Filesystem() ): //WP_Filesystemの初期化
            global $wp_filesystem;//$wp_filesystemオブジェクトの呼び出し

            // 画像のバイナリデータを取得しておく
            $image_data = $wp_filesystem->get_contents($image_url);

            //拡張子をファイル名から取得できるならする
            $tmp_image_url = preg_replace('/\?.*$/i', '', $image_url); //URLの?以降を削除する
            $tmp_extension = preg_replace('/^.*\.([^.]+)$/D', '$1', $tmp_image_url);
            $allow_extensions = array('png', 'jpg', 'jpeg', 'gif' ); //許容する拡張子のリスト

            if ($image_data):
                //もし拡張子がゲットできていなかったら、画像データを精査
                if ( !in_array($tmp_extension, $allow_extensions) ):
                    // MIME Typeをチェック
                    $finfo = finfo_open(FILEINFO_MIME_TYPE);
                    $mime = finfo_buffer($finfo, $image_data);
                    finfo_close($finfo);

                    switch ($mime) { //拡張子の決定
                        case "image/jpeg":
                            $extension = '.jpg';
                            break;
                        case "image/png":
                            $extension = '.png';
                            break;
                        case "image/gif":
                            $extension = '.gif';
                            break;
                        default:
                            echo "対応していない画像ファイル";
                    }

                else: //拡張子が取得できていたら、それを設定する
                    $extension = '.'. $tmp_extension;
                endif;

                //画像を保存する
                $wp_filesystem->put_contents($dir. "cb_" .$image_name. $extension, $image_data);
                //画像を編集する
                //@link: https://developer.wordpress.org/reference/classes/wp_image_editor/
                $image_editor = wp_get_image_editor($dir. "cb_" .$image_name. $extension);
                if ( !is_wp_error($image_editor) ): //画像編集オブジェクトに画像が正常に読み込めたら
                    $image_editor->resize(400, 400, false); //画像のリサイズ
                    $image_editor->save( $dir. "cb_" .$image_name. $extension ); //編集した画像の保存
                else:
                    return;
                    //echo "image_editor error";
                endif;

                $upload_dir = wp_upload_dir();
                return $upload_dir['baseurl'] . "/elink-img/cb_" .$image_name. $extension;

            else: //画像のバイナリデータが取得できていなかったら
                return get_template_directory_uri() . '/images/ogp_no_image.jpg'; //用意しておいた画像を使う
                //echo "画像データ取得できず";
            endif;

        endif; // WP_Filesystem()


    else: // ファイルがあったら、ファイル名を取得する
        //echo 'preg_match作動 すでにファイルあり';
        $filename = array_shift($file_existance); //ローカルパス($dir/$image_name.$extension)形式
        $filename = str_replace($dir,"",$filename);

        $upload_dir = wp_upload_dir();
        return $upload_dir['baseurl'] . "/elink-img/" .$filename;
    endif;
    }

endif;

画像のバイナリデータを精査する処理をなるべく行わずにすむよう、og:imageのURLから拡張子を調べる処理を入れました。

1
2
3
4
//該当部分
$tmp_image_url = preg_replace('/\?.*$/i', '', $image_url); //URLの?以降を削除する
$tmp_extension = preg_replace('/^.*\.([^.]+)$/D', '$1', $tmp_image_url);
$allow_extensions = array('png', 'jpg', 'jpeg', 'gif' ); //許容する拡張子のリスト

これで100〜200msくらいTTFBが早くなりました。

(3)外部リンクのOGP画像表示にキャッシュを利用する

最後にダメ押しで外部リンクのOGP画像についてはキャッシュを利用するようにしました。こちらも「Cocoon」のコードを参考にさせていただきました。

▼ 参考にさせていただいたのは、特にこちらのコード
cocoon/blogcard-out.php at 479b1149e722a35309ef102a5f807f94404b42ee · yhira/cocoon · GitHub

▼ yhiraさんが以前書かれていた記事
WordPressで時間のかかる処理をキャッシュ機能を利用して高速化する方法

WordPressには元々、データベースにデータをキャッシュして利用するための関数があるのですね。これで初めて知りました!

そして、これらのキャッシュ用の関数(set_transient(), get_transient())を、記事文中からブログカードにする部分を検索しHTMLコードに変換する処理を行う関数(cb_original_blog_card())内に追加しました。

こちらも関数部分のプロブラムを全部のせておきます。

cb_original_blog_card
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
* 投稿画面の本文からブログカードに変換する部分をピックアップして、ブログカードのHTMLコードに置換する
* @param   string   the_content(投稿本文)
* @return  string   the_content(ブログカード用に置換された投稿本文)
**/
if (!function_exists('cb_original_blog_card')):
    function cb_original_blog_card ( $the_content ) {

        // 関連情報を取得するための正規表現
        $pattern = '#<span class="bcard (i|e)link"><a href="((?:https?|ftp)(?:://.+?))" title="(.*?)"(?: target="_blank")?>(.*?)</a></span>#';

        // 一致する文字列を取得する
        preg_match_all($pattern, $the_content, $card_matches, PREG_SET_ORDER);
            /* 以下のような配列に格納される
            array(N) {
              [M]=>
                  array(5) {
                    [0]=> mathced content
                    [1]=> $1 : Link type (i|e)
                    [2]=> $2 : URL
                    [3]=> $3 : Title
                    [4]=> $4 : Link Text
                  }
            }*/

        if ($card_matches) { //変換する要素があるならば
            foreach ($card_matches as $m) {
                    $url = $m[2];
                    $domain = parse_url($url, PHP_URL_HOST);

                if ($m[1] == 'i') : //内部リンクの場合
                    $id = url_to_postid( $url ); //IDを取得(URLから投稿ID変換)
                        if ( !$id ) return ''; //IDを取得できない場合は脱出

                    //global $postの利用
                    $blogcard_post      = get_post($id);
                    $title          = $blogcard_post->post_title; //タイトルの取得
                    $excerpt        = get_post_meta($id, 'meta_description', true) ? get_post_meta($id,'meta_description',true) : get_the_excerpt($id); //抜粋の取得
                    if ( has_post_thumbnail($id) ): //thumbnail画像のHTML取得
                        $src    = get_the_post_thumbnail( $id, 'cb400-400', array( 'alt' => $title ) );
                        $thumbnail  = '<div class="blog-card-thumbnail">' .$src. '</div>';
                    else: //thumbnailがない場合
                        $src        = get_template_directory_uri() . '/images/ogp_no_image.jpg';
                        $thumbnail  = '<div class="blog-card-thumbnail"><img src="' .$src. '"></div>';
                    endif;
                    $date           = date('Y-m-d', strtotime($blogcard_post->post_date)); //投稿日の取得
                    $target         = ''; //target="_blank"の設定


                elseif ($m[1] == 'e') : //外部リンクの場合
                    //OGP情報を利用する($graph objectの定義のため、ここで先に読み込む)
                    require_once('opengraph.php'); //@link https://github.com/scottmac/opengraph
                    //キャッシュを利用する                    
                    $url_transient = 'bcard_'. md5($url); //set_transient, get_transient用の変数を設定する
                    $graph = get_transient($url_transient); //キャッシュがないか確認
                    //@link https://developer.wordpress.org/reference/functions/get_transient/

                    if ($graph === false): //キャッシュがなかったらopengraph.phpで情報を取得する
                        $graph = OpenGraph::fetch($url);
                    endif;

                    if (!($graph === false)) : //OGP情報が設定されていたら
                        $title = $graph->title ? $graph->title : $m[3];
                        $excerpt = $graph->description ? $graph->description : '';
                        $src = $graph->image ? cb_fetch_ogp_image_file($graph->image) : get_template_directory_uri().'/images/ogp_no_image.jpg';
                        $thumbnail = '<div class="blog-card-thumbnail"><img class="wp-post-image"  src="' .$src. '"alt="' .$m[3]. '"></div>';

                        set_transient($url_transient, $graph, DAY_IN_SECONDS * 30); //30日間データベースにキャッシュを保存
                        //@link https://developer.wordpress.org/reference/functions/set_transient/
                    endif;
                    $date       = ''; //外部リンクは投稿日なし
                    $target     = 'target="_blank"'; //外部リンクは新規タブで

                endif;

                    //ブログカードのタグを設定
                    $title_tag   = '<section class="blog-card__title">' .$title. '</section>';
                    $date_tag    = '<section class="blog-card__date">' .$date. '</section>';
                    $excerpt_tag = '<section class="blog-card__excerpt">' .$excerpt. '</section>';
                    $domain_tag = '<section class="blog-card__domain">' .$domain. '</section>';
                    //タグを合体
                    $whole_tag = '<div class="blog-card"><a href="' .$url. '"' .$target. '>'.$thumbnail .'<div class="blog-card-body">' .$title_tag  .$date_tag .$excerpt_tag. $domain_tag. '</div></a></div>';

                    //置換する
                    $the_content = preg_replace('{'.preg_quote($m[0]).'}', $whole_tag, $the_content);
            }
        }

        return $the_content;

    }
endif;
add_filter('the_content', 'cb_original_blog_card', 11);

キャッシュは30日間保存される設定にしました。

1
set_transient($url_transient, $graph, DAY_IN_SECONDS * 30);

これでたぶん300msくらいはTTFBが早くなりました。

まとめ

自分のサイトのページ表示速度がワンテンポ遅いな…というのは以前から気になってはいて、Google先生にもその影響があったのかなかったのかわかりませんが、最近のアップデートで冷や飯を食わされ続けているので、これで少しでも良い方向に向かえばいいなと思います。

プラグインなしでのブログカードを表示する記事には地味ながらも一定のアクセスをいただいているので、参考にされる方のお役にたてれば幸いです。

そしてそして、もし少しでもお役にたてましたら、記事をシェア・引用していただけたら嬉しいです(๑′௰‵๑)


1. Times to First Byte の略。Webサーバーにページ表示のリクエストを出してから、そのページ情報の最初の1バイトが返ってくるまでの時間のこと。つまり、これが長いとページ表示速度が遅いということ。 ↩