WordPressでACFを使用したお手軽?SEO機能を実装してみたの巻(下)

Date:

Share post:

前回の投稿 で

  • カスタム投稿タイプ定義
  • タクソノミー定義とターム登録
  • ダミー投稿の登録

までを行っています。前回の投稿に続けて書いていきたいと思います。

5)ACFプラグインをインストール

SEO機能実装でカスタムフィールドを使用するために、ACF( Advanced Custom Fields )プラグインをインストールします。

6)SEO機能の実装

いよいよSEO機能を実装します。まずはソース構成を以下に書きます。

テーマディレクトリ/
    ├── style.css
    ├── functions.php
    ├── header.php
    ├── footer.php
    ├── index.php
    ├── front-page.php
    ├── singular.php
    ├── archive.php
    └── inc/
            ├── cpt.php                   ← CPT 登録を行う
            ├── seo.php                  ← wp_head への description / OGP 出力
            ├── seo-acf.php             ← SEO設定画面( acf_form + option )/CPT編集の SEO タブ
            ├── seo-registry.php      ← 対象ページ一覧(TOP・CPT一覧・固定ページ)
            └── taxonomy.php          ← taxonomy 定義を行う

上の表示と順番が前後しますが、定義( seo-registry.php )→ ACFで入力・保存( seo-acf.php )→ ACFの値を読んで出力( seo.php )という流れになります。それぞれ実装内容とソースを以下に書きます。

 inc/seo-registry.php

SEOの対象ページを定義するファイルです。ACFのフィールドをどのページ向けに出すかの元データとして、個別編集の対象CPTや、SEO設定画面に出す固定ページ一覧などをまとめています。

<?php
/**
 * SEO 対象ページのレジストリ.
 *
 * @package Alma_Demo
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * 個別 SEO フィールドを持つ投稿タイプ(CPT).
 *
 * @return array<int, string>
 */
function alma_demo_get_seo_singular_post_types() {
	return array_keys( alma_demo_get_cpt_definitions() );
}

/**
 * SEO設定画面に出す WordPress 固定ページ一覧.
 *
 * トップページ(フロント指定)は seo_front で別管理するため除外する.
 *
 * @return array<string, array{label:string,path:string,page_id:int}>
 */
function alma_demo_get_seo_wp_pages() {
	$pages = get_posts(
		array(
			'post_type'              => 'page',
			'post_status'            => array( 'publish', 'private', 'draft' ),
			'posts_per_page'         => -1,
			'orderby'                => 'menu_order title',
			'order'                  => 'ASC',
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
		)
	);

	$items   = array();
	$front_id = (int) get_option( 'page_on_front' );

	foreach ( $pages as $page ) {
		$page_id = (int) $page->ID;
		if ( $front_id === $page_id ) {
			continue;
		}

		$uri = get_page_uri( $page_id );
		$key = 'wp_page_' . $page_id;

		$items[ $key ] = array(
			'label'   => get_the_title( $page_id ),
			'path'    => $uri ? $uri : (string) $page->post_name,
			'page_id' => $page_id,
		);
	}

	return $items;
}
 inc/seo-acf.php

ACFを使った入力まわりの実装です。フィールドグループを登録し、管理画面の「SEO設定」では acf_form で option に保存、各CPTの編集画面では投稿メタとしてSEO項目を入力できるようにしています。

<?php
/**
 * ACF SEO フィールドと管理画面.
 *
 * ACF のフィールドグループを登録し、管理メニュー「SEO設定」では acf_form で
 * option に保存、各 CPT 編集画面では投稿メタとして SEO 項目を入力できるようにする.
 *
 * @package Alma_Demo
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * オプション画面用 SEO サブフィールド.
 *
 * @param string $prefix 一意のキー接頭辞.
 * @return array<int, array<string, mixed>>
 */
function alma_demo_acf_seo_sub_fields( $prefix ) {
	return array(
		array(
			'key'   => 'field_' . $prefix . '_description',
			'label' => 'description',
			'name'  => 'description',
			'type'  => 'textarea',
			'rows'  => 3,
		),
		array(
			'key'   => 'field_' . $prefix . '_keywords',
			'label' => 'keywords',
			'name'  => 'keywords',
			'type'  => 'text',
		),
		array(
			'key'          => 'field_' . $prefix . '_og_title',
			'label'        => 'OGPタイトル',
			'name'         => 'og_title',
			'type'         => 'text',
			'instructions' => '空欄の場合はページタイトルを使用します。',
		),
		array(
			'key'          => 'field_' . $prefix . '_og_description',
			'label'        => 'OGP description',
			'name'         => 'og_description',
			'type'         => 'textarea',
			'rows'         => 3,
			'instructions' => '空欄の場合は description を使用します。',
		),
		array(
			'key'           => 'field_' . $prefix . '_og_image',
			'label'         => 'OGP画像',
			'name'          => 'og_image',
			'type'          => 'image',
			'return_format' => 'array',
			'preview_size'  => 'medium',
			'library'       => 'all',
			'instructions'  => '推奨サイズ:1200 × 630 px<br>未設定時はサイト共通のOGP画像を使用します。',
		),
	);
}

/**
 * 1ページ/アーカイブ用の SEO グループフィールド.
 *
 * @param string $key   フィールドキー接尾辞.
 * @param string $name  フィールド名.
 * @param string $label ラベル.
 * @return array<string, mixed>
 */
function alma_demo_acf_seo_group_field( $key, $name, $label ) {
	return array(
		'key'        => 'field_' . $key,
		'label'      => $label,
		'name'       => $name,
		'type'       => 'group',
		'layout'     => 'block',
		'sub_fields' => alma_demo_acf_seo_sub_fields( $key ),
	);
}

/**
 * CPT 個別用 SEO フィールド.
 *
 * name は全 CPT 共通。key のみ CPT ごとに一意にする.
 *
 * @param string $prefix CPT スラッグなど.
 * @return array<int, array<string, mixed>>
 */
function alma_demo_acf_seo_post_fields( $prefix ) {
	$prefix = sanitize_key( $prefix );
	$p      = 'seo_' . $prefix;

	return array(
		array(
			'key'   => 'field_' . $p . '_description',
			'label' => 'description',
			'name'  => 'seo_description',
			'type'  => 'textarea',
			'rows'  => 3,
		),
		array(
			'key'   => 'field_' . $p . '_keywords',
			'label' => 'keywords',
			'name'  => 'seo_keywords',
			'type'  => 'text',
		),
		array(
			'key'          => 'field_' . $p . '_og_title',
			'label'        => 'OGPタイトル',
			'name'         => 'seo_og_title',
			'type'         => 'text',
			'instructions' => '空欄の場合は投稿タイトルを使用します。',
		),
		array(
			'key'          => 'field_' . $p . '_og_description',
			'label'        => 'OGP description',
			'name'         => 'seo_og_description',
			'type'         => 'textarea',
			'rows'         => 3,
			'instructions' => '空欄の場合は description を使用します。',
		),
		array(
			'key'           => 'field_' . $p . '_og_image',
			'label'         => 'OGP画像',
			'name'          => 'seo_og_image',
			'type'          => 'image',
			'return_format' => 'array',
			'preview_size'  => 'medium',
			'library'       => 'all',
			'instructions'  => '推奨サイズ:1200 × 630 px<br>未設定時はアイキャッチ → なければサイト共通のOGP画像を使用します。',
		),
	);
}

/**
 * SEO 用 ACF フィールドグループを登録する.
 */
function alma_demo_register_acf_seo_fields() {
	if ( ! function_exists( 'acf_add_local_field_group' ) ) {
		return;
	}

	// --- オプション画面(TOP / 一覧 / 固定ページ) ---
	$option_fields = array(
		array(
			'key'   => 'field_seo_opt_tab_front',
			'label' => 'トップページ',
			'name'  => '',
			'type'  => 'tab',
		),
		alma_demo_acf_seo_group_field( 'seo_front', 'seo_front', 'トップページ(/)' ),
	);

	$archive_fields = array();
	foreach ( alma_demo_get_cpt_definitions() as $post_type => $config ) {
		if ( empty( $config['has_archive'] ) ) {
			continue;
		}
		$slug  = isset( $config['rewrite']['slug'] ) ? (string) $config['rewrite']['slug'] : $post_type;
		$label = isset( $config['label'] ) ? (string) $config['label'] : $post_type;
		$archive_fields[] = alma_demo_acf_seo_group_field(
			'seo_archive_' . $post_type,
			'seo_archive_' . $post_type,
			$label . '一覧(/' . trim( $slug, '/' ) . '/)'
		);
	}

	if ( $archive_fields ) {
		$option_fields[] = array(
			'key'   => 'field_seo_opt_tab_archives',
			'label' => '一覧ページ',
			'name'  => '',
			'type'  => 'tab',
		);
		foreach ( $archive_fields as $archive_field ) {
			$option_fields[] = $archive_field;
		}
	}

	$wp_pages = alma_demo_get_seo_wp_pages();
	if ( $wp_pages ) {
		$option_fields[] = array(
			'key'   => 'field_seo_opt_tab_wp_pages',
			'label' => '固定ページ',
			'name'  => '',
			'type'  => 'tab',
		);
		foreach ( $wp_pages as $item ) {
			$page_id = isset( $item['page_id'] ) ? (int) $item['page_id'] : 0;
			if ( $page_id < 1 ) {
				continue;
			}
			$path  = isset( $item['path'] ) ? (string) $item['path'] : (string) $page_id;
			$label = isset( $item['label'] ) && '' !== $item['label'] ? (string) $item['label'] : ( '固定ページ #' . $page_id );
			$option_fields[] = alma_demo_acf_seo_group_field(
				'seo_page_' . $page_id,
				'seo_page_' . $page_id,
				$label . '(/' . trim( $path, '/' ) . '/)'
			);
		}
	}

	acf_add_local_field_group(
		array(
			'key'                   => 'group_alma_seo_options',
			'title'                 => 'SEO設定',
			'fields'                => $option_fields,
			'location'              => array(
				array(
					array(
						'param'    => 'options_page',
						'operator' => '==',
						'value'    => 'alma-seo-settings',
					),
				),
			),
			'menu_order'            => 0,
			'position'              => 'normal',
			'style'                 => 'default',
			'label_placement'       => 'top',
			'instruction_placement' => 'label',
			'active'                => true,
		)
	);

	// --- CPT 個別編集画面 ---
	foreach ( alma_demo_get_seo_singular_post_types() as $post_type ) {
		$definitions = alma_demo_get_cpt_definitions();
		$label       = isset( $definitions[ $post_type ]['label'] ) ? $definitions[ $post_type ]['label'] : $post_type;

		acf_add_local_field_group(
			array(
				'key'                   => 'group_alma_seo_post_' . $post_type,
				'title'                 => 'SEO',
				'fields'                => alma_demo_acf_seo_post_fields( $post_type ),
				'location'              => array(
					array(
						array(
							'param'    => 'post_type',
							'operator' => '==',
							'value'    => $post_type,
						),
					),
				),
				'menu_order'            => 20,
				'position'              => 'normal',
				'style'                 => 'default',
				'label_placement'       => 'top',
				'instruction_placement' => 'label',
				'active'                => true,
			)
		);
	}
}
add_action( 'acf/init', 'alma_demo_register_acf_seo_fields' );

/**
 * CPT 編集画面の SEO メタボックスをデフォルトで開く.
 *
 * @param array $classes メタボックスの CSS クラス.
 * @return array
 */
function alma_demo_seo_metabox_open_classes( $classes ) {
	return array_values( array_diff( (array) $classes, array( 'closed' ) ) );
}

/**
 * 各 CPT の SEO メタボックスに「開く」フィルターを付ける.
 */
function alma_demo_force_seo_metabox_open() {
	foreach ( alma_demo_get_seo_singular_post_types() as $post_type ) {
		$box_id = 'acf-group_alma_seo_post_' . $post_type;
		add_filter( "postbox_classes_{$post_type}_{$box_id}", 'alma_demo_seo_metabox_open_classes' );
	}
}
add_action( 'admin_head', 'alma_demo_force_seo_metabox_open' );

/**
 * ユーザー設定で閉じられていても SEO メタボックスは開いた扱いにする.
 *
 * @param mixed $result 閉じているメタボックス ID の配列.
 * @return mixed
 */
function alma_demo_seo_metabox_not_in_closed_list( $result ) {
	if ( ! is_array( $result ) ) {
		return $result;
	}

	$seo_boxes = array();
	foreach ( alma_demo_get_seo_singular_post_types() as $post_type ) {
		$seo_boxes[] = 'acf-group_alma_seo_post_' . $post_type;
	}

	return array_values( array_diff( $result, $seo_boxes ) );
}

/**
 * 各 CPT 画面の closedpostboxes ユーザーオプションを上書きする.
 */
function alma_demo_register_seo_metabox_open_user_option_filters() {
	foreach ( alma_demo_get_seo_singular_post_types() as $post_type ) {
		add_filter( "get_user_option_closedpostboxes_{$post_type}", 'alma_demo_seo_metabox_not_in_closed_list' );
	}
}
add_action( 'admin_init', 'alma_demo_register_seo_metabox_open_user_option_filters' );

/**
 * SEO設定メニューを登録する.
 */
function alma_demo_register_seo_options_page() {
	if ( ! function_exists( 'acf_form' ) ) {
		return;
	}

	add_menu_page(
		'SEO設定',
		'SEO設定',
		'manage_options',
		'alma-seo-settings',
		'alma_demo_render_seo_options_page',
		'dashicons-share',
		null
	);
}
add_action( 'admin_menu', 'alma_demo_register_seo_options_page' );

/**
 * カスタムメニュー並び順を有効化する.
 *
 * @return bool
 */
function alma_demo_enable_custom_menu_order() {
	return true;
}
add_filter( 'custom_menu_order', 'alma_demo_enable_custom_menu_order' );

/**
 * SEO設定を固定ページの直下に配置する.
 *
 * @param array $menu_order メニュースラッグ.
 * @return array
 */
function alma_demo_seo_menu_order( $menu_order ) {
	if ( ! is_array( $menu_order ) ) {
		return $menu_order;
	}

	$seo_slug   = 'alma-seo-settings';
	$menu_order = array_values(
		array_filter(
			$menu_order,
			static function ( $item ) use ( $seo_slug ) {
				return $item !== $seo_slug;
			}
		)
	);

	$pages_index = array_search( 'edit.php?post_type=page', $menu_order, true );
	if ( false === $pages_index ) {
		$menu_order[] = $seo_slug;
		return $menu_order;
	}

	array_splice( $menu_order, (int) $pages_index + 1, 0, array( $seo_slug ) );
	return $menu_order;
}
add_filter( 'menu_order', 'alma_demo_seo_menu_order' );

/**
 * SEO設定画面で ACF フォームの head 処理を読み込む.
 */
function alma_demo_seo_options_acf_form_head() {
	if ( ! is_admin() || ! function_exists( 'acf_form_head' ) ) {
		return;
	}

	// 表示判定のため $_GET['page'] を参照するだけなので、nonce 検証は不要.
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended
	$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
	if ( 'alma-seo-settings' !== $page ) {
		return;
	}

	acf_form_head();
}
add_action( 'admin_init', 'alma_demo_seo_options_acf_form_head' );

/**
 * SEO設定画面を描画する.
 */
function alma_demo_render_seo_options_page() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	if ( ! function_exists( 'acf_form' ) ) {
		echo '<div class="wrap"><p>Advanced Custom Fields が有効ではありません。</p></div>';
		return;
	}
	?>
	<div class="wrap">
		<h1>SEO設定</h1>
		<p>各ページの description / keywords / OGP を設定します。</p>
		<?php
		acf_form(
			array(
				'post_id'         => 'option',
				'field_groups'    => array( 'group_alma_seo_options' ),
				'submit_value'    => '変更を保存',
				'updated_message' => 'SEO設定を保存しました。',
			)
		);
		?>
	</div>
	<?php
}
 inc/seo.php

フロントの出力処理です。ACFの  get_field() で保存値を読み取り、現在のページに応じて解決したうえで、wp_head に description / keywords / OGP などの meta タグを出力します。

<?php
/**
 * フロント用 SEO メタ出力(description / keywords / OGP).
 *
 * @package Alma_Demo
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * 投稿フィールドまたはオプショングループから SEO 値を正規化する.
 *
 * @param array<string,mixed> $raw 生の値.
 * @return array{description:string,keywords:string,og_title:string,og_description:string,og_image_url:string}
 */
function alma_demo_normalize_seo_values( $raw ) {
	$image_url = '';
	if ( ! empty( $raw['og_image'] ) && is_array( $raw['og_image'] ) && ! empty( $raw['og_image']['url'] ) ) {
		$image_url = (string) $raw['og_image']['url'];
	} elseif ( ! empty( $raw['og_image'] ) && is_numeric( $raw['og_image'] ) ) {
		$image_url = (string) wp_get_attachment_image_url( (int) $raw['og_image'], 'full' );
	} elseif ( ! empty( $raw['og_image_url'] ) ) {
		$image_url = (string) $raw['og_image_url'];
	}

	return array(
		'description'    => isset( $raw['description'] ) ? trim( (string) $raw['description'] ) : '',
		'keywords'       => isset( $raw['keywords'] ) ? trim( (string) $raw['keywords'] ) : '',
		'og_title'       => isset( $raw['og_title'] ) ? trim( (string) $raw['og_title'] ) : '',
		'og_description' => isset( $raw['og_description'] ) ? trim( (string) $raw['og_description'] ) : '',
		'og_image_url'   => $image_url,
	);
}

/**
 * SEO オプショングループを読み込む.
 *
 * @param string $group_name オプション上のフィールドグループ名(例: seo_front).
 * @return array{description:string,keywords:string,og_title:string,og_description:string,og_image_url:string}
 */
function alma_demo_get_seo_option_group( $group_name ) {
	$raw = array();
	if ( function_exists( 'get_field' ) ) {
		$value = get_field( $group_name, 'option' );
		if ( is_array( $value ) ) {
			$raw = $value;
		}
	}

	return alma_demo_normalize_seo_values( $raw );
}

/**
 * サイト共通のデフォルト OGP 画像 URL.
 *
 * @return string
 */
function alma_demo_get_default_og_image_url() {
	$path = get_template_directory() . '/assets/ogp.jpg';
	if ( file_exists( $path ) ) {
		return get_template_directory_uri() . '/assets/ogp.jpg';
	}

	$site_icon = get_site_icon_url( 512 );
	return $site_icon ? $site_icon : '';
}

/**
 * 投稿のフォールバック画像(アイキャッチ).
 *
 * @param int $post_id 投稿 ID.
 * @return string
 */
function alma_demo_get_seo_fallback_image_url( $post_id ) {
	$thumb = get_the_post_thumbnail_url( $post_id, 'full' );
	return $thumb ? $thumb : '';
}

/**
 * OGP 画像を確保する.
 *
 * 優先順位: 明示的な OGP → アイキャッチ → サイト共通デフォルト.
 *
 * @param array $meta    SEO メタ.
 * @param int   $post_id 投稿 ID.
 * @return array
 */
function alma_demo_ensure_seo_og_image( $meta, $post_id = 0 ) {
	if ( ! empty( $meta['og_image_url'] ) ) {
		return $meta;
	}

	if ( $post_id ) {
		$fallback = alma_demo_get_seo_fallback_image_url( (int) $post_id );
		if ( $fallback ) {
			$meta['og_image_url'] = $fallback;
			return $meta;
		}
	}

	$meta['og_image_url'] = alma_demo_get_default_og_image_url();
	return $meta;
}

/**
 * 現在のリクエスト向け SEO 値を解決する.
 *
 * @return array{description:string,keywords:string,og_title:string,og_description:string,og_image_url:string,page_title:string}
 */
function alma_demo_resolve_seo_meta() {
	$empty = array(
		'description'    => '',
		'keywords'       => '',
		'og_title'       => '',
		'og_description' => '',
		'og_image_url'   => '',
		'page_title'     => wp_get_document_title(),
	);

	if ( is_front_page() ) {
		$meta               = alma_demo_get_seo_option_group( 'seo_front' );
		$meta['page_title'] = $empty['page_title'];
		return alma_demo_ensure_seo_og_image( $meta );
	}

	if ( is_page() ) {
		$page_id            = get_queried_object_id();
		$meta               = alma_demo_get_seo_option_group( 'seo_page_' . $page_id );
		$meta['page_title'] = get_the_title( $page_id );
		return alma_demo_ensure_seo_og_image( $meta, $page_id );
	}

	$seo_post_types = alma_demo_get_seo_singular_post_types();
	if ( $seo_post_types && is_singular( $seo_post_types ) ) {
		$post_id = get_queried_object_id();
		$raw     = array(
			'description'    => function_exists( 'get_field' ) ? (string) get_field( 'seo_description', $post_id ) : '',
			'keywords'       => function_exists( 'get_field' ) ? (string) get_field( 'seo_keywords', $post_id ) : '',
			'og_title'       => function_exists( 'get_field' ) ? (string) get_field( 'seo_og_title', $post_id ) : '',
			'og_description' => function_exists( 'get_field' ) ? (string) get_field( 'seo_og_description', $post_id ) : '',
			'og_image'       => function_exists( 'get_field' ) ? get_field( 'seo_og_image', $post_id ) : null,
		);
		$meta               = alma_demo_normalize_seo_values( $raw );
		$meta['page_title'] = get_the_title( $post_id );
		return alma_demo_ensure_seo_og_image( $meta, $post_id );
	}

	foreach ( alma_demo_get_cpt_definitions() as $post_type => $config ) {
		if ( empty( $config['has_archive'] ) ) {
			continue;
		}
		if ( ! is_post_type_archive( $post_type ) && ! is_tax( $post_type . '_category' ) ) {
			continue;
		}
		$meta               = alma_demo_get_seo_option_group( 'seo_archive_' . $post_type );
		$meta['page_title'] = $empty['page_title'];
		return alma_demo_ensure_seo_og_image( $meta );
	}

	return alma_demo_ensure_seo_og_image( $empty );
}

/**
 * wp_head にメタタグを出力する.
 */
function alma_demo_output_seo_meta() {
	$meta = alma_demo_resolve_seo_meta();

	$description = $meta['description'];
	$keywords    = $meta['keywords'];
	$og_title    = $meta['og_title'] ? $meta['og_title'] : $meta['page_title'];
	$og_desc     = $meta['og_description'] ? $meta['og_description'] : $description;
	$og_image    = $meta['og_image_url'];

	$og_url = home_url( '/' );
	if ( is_singular() ) {
		$permalink = get_permalink();
		if ( $permalink ) {
			$og_url = $permalink;
		}
	} elseif ( isset( $_SERVER['REQUEST_URI'] ) ) {
		$path = (string) wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH );
		if ( $path ) {
			$og_url = home_url( $path );
		}
	}

	if ( $description ) {
		echo '<meta name="description" content="' . esc_attr( $description ) . '">' . "\n";
	}
	if ( $keywords ) {
		echo '<meta name="keywords" content="' . esc_attr( $keywords ) . '">' . "\n";
	}

	$og_type = ( is_singular() && ! is_front_page() ) ? 'article' : 'website';

	echo '<meta property="og:type" content="' . esc_attr( $og_type ) . '">' . "\n";
	echo '<meta property="og:site_name" content="' . esc_attr( get_bloginfo( 'name' ) ) . '">' . "\n";
	if ( $og_title ) {
		echo '<meta property="og:title" content="' . esc_attr( $og_title ) . '">' . "\n";
	}
	if ( $og_desc ) {
		echo '<meta property="og:description" content="' . esc_attr( $og_desc ) . '">' . "\n";
	}
	if ( $og_url ) {
		echo '<meta property="og:url" content="' . esc_url( $og_url ) . '">' . "\n";
	}
	if ( $og_image ) {
		echo '<meta property="og:image" content="' . esc_url( $og_image ) . '">' . "\n";
	}

	echo '<meta name="twitter:card" content="' . esc_attr( $og_image ? 'summary_large_image' : 'summary' ) . '">' . "\n";
	if ( $og_title ) {
		echo '<meta name="twitter:title" content="' . esc_attr( $og_title ) . '">' . "\n";
	}
	if ( $og_desc ) {
		echo '<meta name="twitter:description" content="' . esc_attr( $og_desc ) . '">' . "\n";
	}
	if ( $og_image ) {
		echo '<meta name="twitter:image" content="' . esc_url( $og_image ) . '">' . "\n";
	}
}
add_action( 'wp_head', 'alma_demo_output_seo_meta', 5 );

/**
 * SEO の OGPタイトルが設定されているとき、ブラウザの title にも反映する.
 *
 * @param string $title ドキュメントタイトル.
 * @return string
 */
function alma_demo_filter_document_title_from_seo( $title ) {
	static $busy = false;

	if ( $busy || is_admin() ) {
		return $title;
	}

	$busy = true;
	$meta = alma_demo_resolve_seo_meta();
	$busy = false;

	if ( ! empty( $meta['og_title'] ) ) {
		return $meta['og_title'];
	}

	return $title;
}
add_filter( 'pre_get_document_title', 'alma_demo_filter_document_title_from_seo', 99 );

上記実装が完了しますと、管理画面の方に SEO 設定が追加され、各投稿の編集画面に SEO 情報の設定項目が追加されます。

左メニューに「 SEO 設定」が追加されています。

SEO 設定画面で各ページの SEO 項目が入力できるようになっています。

投稿編集ページでも、同じように SEO 入力項目がメタボックスで追加されています。

7)meta情報の設定

入力項目が用意できたので、それぞれの項目に設定します。設定方法は手動入力や、前回の投稿 で話が出た WP-CLI で登録することもできると思います。内容は割愛しますが、結果表示は以下のとおりです。

TOPページの設定内容です。

一覧ページの設定内容です。

投稿編集画面のメタボックスへの設定内容です。

8)meta情報出力の確認

上記の通り設定したので、実際に出力されていることを確認してみましょう。

metaタグが出力されて、設定した内容になっていますね。

前回(上巻)、今回(下巻)と書いてきましたが、ACF を使って基本的なSEO設定(description / OGP)を管理画面から入れ、テーマ側で出力する、という一通りの流れをまとめました。

SEO専用プラグインほど多機能にはせず、かといって入力 UI まで完全自前にもしない、この「ほどよい折衷」が、実務ではいちばん使いやすいことが多いと感じています。AEO の話が増えるいまも、title / description / OGP をきちんと出せる土台は変わらず大事です。

同じような悩みを持っている方の参考になれば幸いです。

Related articles