LLMO時代に向けた WordPress リニューアル(理由と実装)

対象読者: WordPress+Nginx を自前運用しているエンジニア(VPS/自宅サーバー等)
やること: AIクローラ配慮・REST安定化・ライセンス明示・機械可読インデックスの整備
狙い(LLMO): 人間読者だけでなく LLM/AI クローラの一次読者にも正確・安全に情報を届ける


0) 前提と置換ルール(必ず自分の環境に直す)

以下は Nginx + php-fpm、ドキュメントルートが /var/www/<domain> の一般的な構成を想定。

# ← ここをあなたのサイトに合わせて書き換える
export SITE_DOMAIN="papanda925.com"          # 例: yourdomain.tld
export SITE_URL="https://$SITE_DOMAIN"
export WEBROOT="/var/www/$SITE_DOMAIN"       # WPのルート(index.php がある場所)

本文中の example.com$SITE_DOMAIN に読み替え。
ファイル編集は root 権限または適切な sudo で実施。


1) なぜこれが「LLMO対応」なのか(要点)

  • AIクローラに配慮
    GPTBot / ClaudeBot / Google-Extended / Perplexity などが実際にサイトを巡回。
    robots.txt / llms.txt / ai-index.json を整備し、参照して良い範囲・出典方針・機械可読の正規インデックスを提示すると、誤引用抑止・正確な取り込みにつながる。
  • REST API を安定化
    自動投稿や将来の RAG/外部連携は WordPress REST が土台。
    リライトやプラグイン干渉で /wp-json/... が HTML 返しになる事故を避けるため、
    /index.php?rest_route= に統一して 常に JSON を保証 → 壊れにくい基盤に。
  • ライセンスと出典の明示
    記事末尾に CC BY 4.0 を自動表示(人間向け)。
    llms.txtai-index.json でも方針とライセンスを機械可読に提示(AI向け)。
    安心して引用・再利用できる条件を二重に明示。

2) AIクローラ対応(robots.txt / llms.txt / ai-index.json)

2-1. robots.txt(AI含むクローラ許可+Sitemap 案内)

sudo tee "$WEBROOT/robots.txt" >/dev/null <<'EOF'
User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Applebot-Extended
Allow: /

User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml
EOF

# 自サイトURLに置換
sed -i "s#https://example.com/#$SITE_URL/#" "$WEBROOT/robots.txt"

2-2. llms.txt(LLM向け利用方針)

sudo tee "$WEBROOT/llms.txt" >/dev/null <<'EOF'
# Location: https://example.com/llms.txt
policy: allow-crawl; prefer-citation; contact: admin@example.com
sitemap: https://example.com/sitemap.xml
ai-index: https://example.com/ai-index.json
license: https://example.com/policy
EOF

sed -i "s#https://example.com/#$SITE_URL/#; s#admin@example.com#admin@$SITE_DOMAIN#" "$WEBROOT/llms.txt"

2-3. ai-index.json を出す(mu-plugin で機械可読インデックス)

sudo -u www-data mkdir -p "$WEBROOT/wp-content/mu-plugins"
sudo -u www-data tee "$WEBROOT/wp-content/mu-plugins/ai-index.php" >/dev/null <<'PHP'
<?php
/**
 * Plugin Name: AI Index (mu)
 * Description: Publish a minimal JSON index of posts for LLMs/RAG.
 * Version: 1.0.0
 */
if (!defined('ABSPATH')) exit;

add_action('rest_api_init', function () {
  register_rest_route('ai/v1', '/index', [
    'methods'  => 'GET',
    'callback' => function () {
      $q = new WP_Query([
        'post_type'      => 'post',
        'posts_per_page' => 200,
        'orderby'        => 'modified',
        'order'          => 'DESC',
        'post_status'    => 'publish',
      ]);
      $items = [];
      while ($q->have_posts()) { $q->the_post();
        $excerpt = get_the_excerpt();
        if (empty($excerpt)) {
          $excerpt = wp_trim_words(wp_strip_all_tags(get_the_content()), 60, '…');
        }
        $items[] = [
          'url'     => get_permalink(),
          'title'   => get_the_title(),
          'summary' => wp_strip_all_tags($excerpt),
          'lastmod' => get_the_modified_time('Y-m-d'),
          'tags'    => wp_get_post_tags(get_the_ID(), ['fields' => 'names']),
          'license' => 'CC BY 4.0',
        ];
      }
      wp_reset_postdata();
      return [
        'site'    => home_url(),
        'updated' => date('Y-m-d'),
        'items'   => $items,
      ];
    },
    'permission_callback' => '__return_true',
  ]);
});
PHP

(任意)/ai-index.json という固定パスで出したい場合:Nginx でリライト

# /etc/nginx/sites-available/$SITE_DOMAIN の server { ... } 内に追加
location = /ai-index.json {
    rewrite ^ /index.php?rest_route=/ai/v1/index last;
}

反映と確認:

sudo nginx -t && sudo systemctl reload nginx

curl -I "$SITE_URL/robots.txt"
curl -I "$SITE_URL/llms.txt"
curl -I "$SITE_URL/ai-index.json"      # 200 + application/json が目安
curl -s "$SITE_URL/ai-index.json" | head

3) REST API の安定化(/index.php?rest_route= に統一)

背景: /wp-json/... がテーマ/プラグイン/リライトで HTML を返す事故を回避。
方針: クライアント(スクリプト)からの呼び出しは すべて index.php?rest_route= を使う

Python クライアント例(抜粋)

from requests.auth import HTTPBasicAuth
import requests, os
from markdown2 import markdown

WP_URL = os.getenv("WP_URL")          # 例: https://papanda925.com
WP_USER = os.getenv("WP_USER")
WP_APP_PASSWORD = os.getenv("WP_APP_PASSWORD")

def _posts_api_base():
    return f"{WP_URL.rstrip('/')}/index.php?rest_route=/wp/v2/posts"

HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}

def _ensure_json_response(resp):
    ct = resp.headers.get("Content-Type", "")
    if "application/json" not in ct:
        preview = (resp.text or "")[:200].replace("\n", "\\n")
        raise RuntimeError(f"Unexpected Content-Type: {ct}. Preview: {preview!r}")

def post_to_wordpress(title: str, markdown_content: str, status: str = "publish"):
    html = markdown(markdown_content)
    data = {"title": title, "content": html, "status": status}
    r = requests.post(_posts_api_base(), headers=HEADERS,
                      auth=HTTPBasicAuth(WP_USER, WP_APP_PASSWORD),
                      json=data, timeout=30)
    r.raise_for_status()
    _ensure_json_response(r)
    print("OK", r.json().get("id"), r.json().get("link"))

動作確認

curl -i -H "Accept: application/json" \
  "$SITE_URL/index.php?rest_route=/wp/v2/posts&per_page=1"
# → HTTP/2 200 + Content-Type: application/json

よくある落とし穴

  • 認証/WAF系プラグインが REST を塞ぐ → ホワイトリスト/許可設定を入れる
  • リダイレクト多発 → -L で追っても最終 text/html なら index.php?rest_route= に切替

4) ライセンス・出典の明示(mu-plugin で末尾ボックス)

目的: 人間にも AI にも、「再利用条件」「出典表記」を明確化。
実装: 投稿本文の末尾に CC BY 4.0 告知を自動付与。管理画面/REST/固定ページや特定スラッグは除外。

sudo -u www-data tee "$WEBROOT/wp-content/mu-plugins/license-box.php" >/dev/null <<'PHP'
<?php
/**
 * Plugin Name: License Box (mu)
 * Description: Append a license notice box to single posts.
 * Version: 1.0.0
 */
if (!defined('ABSPATH')) exit;

function _p925_should_show_box(){
  if (is_admin()) return false;
  if (defined('REST_REQUEST') && REST_REQUEST) return false;
  if (!is_singular('post')) return false;
  if (!in_the_loop() || !is_main_query()) return false;
  $exclude_slugs = ['policy','privacy-policy','about'];
  $slug = get_post_field('post_name', get_queried_object_id());
  if (in_array($slug, $exclude_slugs, true)) return false;
  return true;
}
function _p925_box_html(){
  $policy  = esc_url(home_url('/policy'));
  $lic     = 'https://creativecommons.org/licenses/by/4.0/';
  return '<div class="license-box" style="margin-top:2rem;padding:1rem;border:1px solid #e5e7eb;border-radius:.75rem;background:#fafafa">
<strong>ライセンス</strong>:本文・コードは特記なき限り <a href="'.$lic.'" target="_blank" rel="noopener">CC BY 4.0</a>。引用は出典URLを明記してください。<br>
<a href="'.$policy.'">利用ポリシー</a> も参照。
</div>';
}
add_filter('the_content', function($content){
  if (!_p925_should_show_box()) return $content;
  return $content . _p925_box_html();
}, 99);
PHP

確認: 任意の投稿ページ末尾に枠が表示(policy 等は除外)。


5) Nginx の最小追記と検証

# /etc/nginx/sites-available/$SITE_DOMAIN の server { ... } へ
# 固定パスでの JSON インデックス公開
location = /ai-index.json {
    rewrite ^ /index.php?rest_route=/ai/v1/index last;
}

# テキスト系を明示(任意)
location = /llms.txt {
    default_type text/plain;
    try_files /llms.txt =404;
}

反映とヘッダ確認:

sudo nginx -t && sudo systemctl reload nginx

curl -I "$SITE_URL/wp-json/"         # 200 または 301→最終200
curl -I "$SITE_URL/ai-index.json"    # 200 + application/json
curl -I "$SITE_URL/llms.txt"         # 200 + text/plain
curl -I "$SITE_URL/robots.txt"       # 200

6) 監視・検証コマンド(AIクローラ来訪の把握)

# 直近で AI クローラだけ抽出
zgrep -hE "GPTBot|ClaudeBot|Google-Extended|PerplexityBot|Applebot-Extended" \
  /var/log/nginx/access.log* | tail -n 100

# UA ランキング(だいたいの傾向)
zgrep -h . /var/log/nginx/access.log* | \
  awk -F\" '{print $6}' | sort | uniq -c | sort -nr | head

7) チェックリスト

  • robots.txt / llms.txt / ai-index.json200
  • ai-index.jsonitems に最新記事が反映
  • 投稿末尾の ライセンス枠 が表示(除外ページでは出ない)
  • index.php?rest_route=REST が常時 JSON
  • nginx -t OK → リロード後ログにエラーなし
  • アクセスログに AIクローラUA の来訪を確認

8) ロールバック方法

  • Nginx: 事前バックアップ(*.bak.YYYY-MM-DD)へ差し替え → nginx -t && reload
  • mu-plugins: ai-index.php / license-box.php を一時リネーム(.off 等)で停止
  • ルートファイル: robots.txt / llms.txt を退避または削除

9) まとめ(LLMO視点の最終要点)

  • 供給: ai-index.json軽量・正規・機械可読なデータソースを提供
  • 方針: llms.txt記事末尾ボックスで再利用条件・出典ルールを明示
  • 耐性: index.php?rest_route=REST 経路を安定化(RAG/自動化の土台)

これで「AIに正しく読まれ、正しく引用される」WordPress基盤が整います。
以降は記事品質やタグ設計、更新頻度の最適化など “中身のLLMO” に集中できます。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました