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

ブログカード用のオリジナル関数を読み込む位置を変えてみたり、外部リンク用にキャッシュを設定したりした結果、1秒以上は早く表示されるようになりました。

ブログカードの生成方法

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

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

//投稿画面でこのように書いておく
<span class="bcard ilink"><a href="https://cosybench.com/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)を読み込ませてしまっていました。

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

また、ブログカード生成の関数は個々の記事でしか使用しないにも関わらず、全ページで読み込まれることにもなっていたので、読み込みのタイミングを変更しました。

//修正した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()などの判定が使えないということ。

そこで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テーマです。

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

/**
* 外部サイトの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から拡張子を調べる処理を入れました。

//該当部分
$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」のコードを参考にさせていただきました。

WordPressにもともとデータベースにデータをキャッシュして利用するための関数があるのですね。それらの関数(set_transient(), get_transient())を、記事文中からブログカードにする部分をピックアップしてHTMLコードに変換する処理を行う関数(cb_original_blog_card())内に追加しました。

こちらも全部のせます。

/**
* 投稿画面の本文からブログカードに変換する部分をピックアップして、ブログカードの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日間保存される設定にしました。

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

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

まとめ

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

プラグインなしでのブログカードを表示する記事には地味ながらも一定のアクセスをいただいているので、参考にされる方のお役にたてれば幸いです。そしてもし少しでもお役にたてましたら、記事をシェア・引用していただけたら嬉しいです(๑′௰‵๑)

▼ 参考にさせていただいた外部サイトをブログカードでも!

▼ 自作ブログカードに関する過去記事




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