WordPressサイトのSEO改善をしようとすると、「とりあえずSEOプラグインを入れておけばいい」という判断をしがちです。

実際、CODE PLUS.jp では VK All in One Expansion Unit(VK ExUnit)というプラグインで構造化データを出力していましたが、ソースを確認してみると意外なほど薄い内容しか出力されていませんでした。

この記事では、head.php に構造化データを自前で実装することで、どのような問題を解決したかを実例とコードつきで解説します。


    プラグイン任せの構造化データの実態

    まず、VK ExUnit で「構造化データ - WebSite」にチェックを入れていた状態でソースを確認しました。

    {"@context":"https://schema.org/","@type":"WebSite","name":"CODE-PLUS","url":"https://code-plus.jp/gp"}
    

    出力されてはいます。ただし descriptionpotentialAction(SearchAction)も何もない、最低限の2プロパティだけの状態でした。

    また、パンくずリストの構造化データについてもチェックを入れていましたが、TOPページでは:

    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {"@type": "ListItem", "position": 1, "name": "FRONT(トップページ)", "item": "https://code-plus.jp/gp"}
      ]
    }
    

    自分自身しかないリスト、つまりGoogleに構造を伝えられていない状態でした。

    さらに、記事ページでは構造化データ自体が 0件 という状況もありました(VK ExUnit の「構造化データ - 記事」を以前 OFF にしていたため)。

    同時に発見したその他の問題

    ソースを精査する中で、構造化データ以外にも問題が見つかりました。

    問題内容
    titleタグが異常に長いVK ExUnit が「サイト名 | キャッチフレーズ(全文)」を出力。200文字超になっていた
    h1タグが存在しないTOPページがロゴ・画像中心の構成で、テキストのh1が抜けていた
    canonicalタグがない/gp/gp/ の重複コンテンツ問題が起きる可能性があった
    meta descriptionが重複出力VK ExUnit と head.php の両方から出力されていた
    パンくずのHTML構造が壊れていたカテゴリーリンクをコメントアウトしたとき </a></li> だけ残っていた

    「プラグインを入れているから大丈夫」という思い込みが、複数の見落としを生んでいました。


    対応方針:structured-data.php に一元化する

    head.php に直接べた書きしていくと管理が煩雑になるため、commons/structured-data.php というファイルを新設してすべての構造化データをまとめることにしました。

    /* head.php の wp_head() 直後に1行追加するだけ */
    <?php include do_shortcode('/home/peysrwib/public_html/gp/wp-content/themes/zaratan') . '/commons/structured-data.php'; ?>
    

    VK ExUnit 側は「構造化データ - WebSite / 記事 / パンくずリスト」の3つをすべてOFFにして、head.php 側で完全に管理する構成に切り替えました。


    実装① canonical タグ

    https://code-plus.jp/gphttps://code-plus.jp/gp/ のどちらでアクセスしても同じページが表示される場合、Googleが別ページとして認識して評価が分散する可能性があります。canonical を明示することでどちらが正規URLかを伝えます。

    /* structured-data.php */
    
    <!-- ▼SEO:canonical -->
    <?php
    $canonical_url = '';
    if (is_front_page()) {
      $canonical_url = trailingslashit(home_url());
    } elseif (is_singular()) {
      $canonical_url = get_permalink();
    } elseif (is_category() || is_tag() || is_tax()) {
      $term_link = get_term_link(get_queried_object());
      if (!is_wp_error($term_link)) {
        $canonical_url = $term_link;
      }
    }
    if ($canonical_url) : ?>
      <link rel="canonical" href="<?php echo esc_url($canonical_url); ?>">
    <?php endif; ?>
    

    実装② WebSite + SearchAction(TOPページ)

    TOPページにだけ出力します。potentialAction に SearchAction を追加することで、Google の検索結果にサイト内検索ボックス(サイトリンク検索ボックス)が表示される可能性が出てきます。

    description は TOPページの「抜粋(meta description と同じ)」から動的取得することで、抜粋を書き換えるだけで description と構造化データが同時に更新される設計にしています。

    <?php if (is_front_page()) : ?>
    <?php
    global $post;
    $ld_description = ($post && has_excerpt()) ? get_the_excerpt() : '';
    ?>
    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "WebSite",
      "name": "<?php echo esc_js(get_bloginfo('name')); ?>",
      "url": "<?php echo esc_url(trailingslashit(home_url())); ?>"
      <?php if ($ld_description) : ?>
      ,"description": "<?php echo esc_js($ld_description); ?>"
      <?php endif; ?>
      ,"inLanguage": "ja"
      ,"potentialAction": {
        "@type": "SearchAction",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "<?php echo esc_url(home_url()); ?>/?s={search_term_string}"
        },
        "query-input": "required name=search_term_string"
      }
    }
    </script>
    <?php endif; ?>
    

    実装③ TechArticle(記事ページ)

    記事ページには TechArticle スキーマを使います。Article でも間違いではありませんが、CODE PLUS のコンテンツは技術手順・実装解説が中心なので TechArticle の方が実態に近く、リッチリザルトで「技術情報」ラベルが表示されやすくなります。

    アイキャッチ画像・公開日・更新日・著者名・サイト名をすべて含めた形で出力します。

    <?php elseif (is_single() && get_post_type() === 'post') : ?>
    <?php
    $article_title    = get_the_title();
    $article_url      = get_permalink();
    $article_date     = get_the_date('c');
    $article_modified = get_the_modified_date('c');
    $article_desc     = ($post && has_excerpt()) ? get_the_excerpt() : '';
    $author_name      = get_the_author();
    $site_name        = get_bloginfo('name');
    $site_url         = trailingslashit(home_url());
    $thumbnail_url    = has_post_thumbnail() ? get_the_post_thumbnail_url(null, 'large') : '';
    ?>
    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "TechArticle",
      "headline": "<?php echo esc_js($article_title); ?>",
      "url": "<?php echo esc_url($article_url); ?>"
      <?php if ($article_desc) : ?>
      ,"description": "<?php echo esc_js($article_desc); ?>"
      <?php endif; ?>
      <?php if ($thumbnail_url) : ?>
      ,"image": "<?php echo esc_url($thumbnail_url); ?>"
      <?php endif; ?>
      ,"datePublished": "<?php echo esc_attr($article_date); ?>"
      ,"dateModified": "<?php echo esc_attr($article_modified); ?>"
      ,"author": {"@type": "Person", "name": "<?php echo esc_js($author_name); ?>"}
      ,"publisher": {"@type": "Organization", "name": "<?php echo esc_js($site_name); ?>", "url": "<?php echo esc_url($site_url); ?>"}
      ,"inLanguage": "ja"
    }
    </script>
    <?php endif; ?>
    

    実装④ BreadcrumbList(パンくず構造化データ)

    パンくずリストは UI として表示するだけでなく、構造化データとしてGoogleに伝えることで検索結果上にパンくずが表示されるようになります。

    breadcrumb.php の表示ロジックと完全に対応させています。

    ページ種別パンくず構造
    投稿ページトップ > 投稿一覧 > カテゴリー > 記事名
    カスタム投稿トップ > カスタム投稿一覧 > カテゴリー > 記事名
    アーカイブトップ > アーカイブ
    固定ページトップ > (先祖ページ) > 現在ページ
    TOP・404・検索結果出力しない
    <!-- ▼SEO:BreadcrumbList(TOPページ・404・検索結果以外) -->
    <?php if (!is_front_page() && !is_404() && !is_search()) : ?>
    <?php
    global $post;
    $home_url = trailingslashit(do_shortcode('https://code-plus.jp/gp'));
    $bc_items = [];
    $position = 1;
    
    $bc_items[] = ['pos' => $position++, 'name' => 'トップ', 'url' => $home_url];
    
    if (is_single() && get_post_type() === 'post') {
      $bc_items[] = ['pos' => $position++, 'name' => '投稿一覧', 'url' => $home_url . 'postlist/'];
      $cat_slug = do_shortcode('wordpress');
      $cat_name = do_shortcode('WordPress');
      if ($cat_slug && $cat_name) {
        $bc_items[] = ['pos' => $position++, 'name' => wp_strip_all_tags($cat_name), 'url' => $home_url . 'category/' . $cat_slug . '/'];
      }
      $bc_items[] = ['pos' => $position++, 'name' => wp_strip_all_tags(get_the_title()), 'url' => get_permalink()];
      // カスタム投稿・アーカイブ・固定ページのパターンは省略
    }
    
    $ld_items = [];
    foreach ($bc_items as $item) {
      $ld_items[] = '{"@type":"ListItem","position":' . $item['pos'] . ',"name":"' . esc_js($item['name']) . '","item":"' . esc_url($item['url']) . '"}';
    }
    ?>
    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "BreadcrumbList",
      "itemListElement": [<?php echo implode(',', $ld_items); ?>]
    }
    </script>
    <?php endif; ?>
    

    meta description の重複問題と運用設計

    VK ExUnit の「メタディスクリプションタグを出力」がONの場合、抜粋があると VK ExUnit が <meta name="description"> を出力します。head.php 側でも同じ内容を出力するコードが残っていると重複出力になります。

    以下の条件分岐で、「抜粋がない場合のみ head.php 側が出力する」構成にしました。

    <?php
    // 抜粋がある → VK ExUnitが出力するので head.php 側は出力しない
    // 抜粋がない → 「○○ページです。」を自動生成して出力
    $auto_desc = generate_meta_description();
    if (!($post && has_excerpt()) && !empty($auto_desc)) : ?>
      <meta name="description" content="<?php echo esc_attr($auto_desc); ?>">
    <?php endif; ?>
    

    これで description の管理フローはシンプルになります。

    設定運用方法
    キャッチフレーズ(一般設定)常に空のまま
    TOPページ titleVK ExUnit の「titleタグ設定」入力欄で管理
    全ページ description抜粋に書く。未記入なら「○○ページです。」が自動生成
    WebSite descriptionTOPページの抜粋から動的取得(= meta description と同じ内容)

    実装後の確認方法

    Google リッチリザルトテストにURLを貼り付けると、構造化データが正しく認識されているか確認できます。記事ページでは TechArticle と BreadcrumbList の両方が検出されれば成功です。

    SearchAction の反映(サイトリンク検索ボックス)は数週間〜数ヶ月かかるので、実装後はしばらく様子を見ます。


    まとめ

    「SEOプラグインを入れている = 対策済み」という状態から、structured-data.php に一元化することで以下が整いました。

    • ✅ canonical タグ(全ページ)
    • ✅ WebSite + SearchAction(TOPページ)
    • ✅ TechArticle(記事ページ)
    • ✅ BreadcrumbList(全ページ・条件分岐あり)
    • ✅ meta description の重複解消と運用設計

    実装量はそれほど多くありませんが、ソースを確認する前と後では、Googleに伝えられる情報量が全然違います。まだ構造化データを入れていないWordPressサイトがあれば、まずソースを確認してみることをお勧めします。