【WordPress】AI 検索向けに llms.txt と IndexNow をプラグインなしで足した
検索エンジンや AI の検索に、新しい記事を早く、正しく載せてもらいたい。そう思って、僕の WordPress の共通のプラグイン(NARI Base)に、llms.txt と IndexNow の2つを足しました。どちらも配布のプラグインは使わず、PHP だけで作っています。この記事では、それぞれが何をするものかと、実装のポイントをまとめます。
- llms.txt と IndexNow が、それぞれ何をするものか(Google 検索での扱いも)
- llms.txt を、ファイルを置かずに WordPress から返す方法
- Yoast SEO の説明文を llms.txt に使うときの落とし穴
- IndexNow で、公開・更新した URL を1回の保存で1回だけ送る方法
llms.txt と IndexNow の違い
- llms.txt
-
AI にサイトの中身を短く伝えるための、Markdown のファイルです(
/llms.txtに置く)。サイト名・説明・主なページの一覧などを書きます。提案されている形式で、すべての AI が読むと決まっているわけではありません。 - IndexNow
-
記事を公開・更新したときに、その URL を検索エンジンにすぐ知らせる仕組みです。Bing や Yandex などが使っています。知らせないと、検索エンジンが次に見に来るまで、新しい記事が載りません。
llms.txt は、Google 検索(AI の機能も含む)では使われていません。Google の公式の文書にも、AI 向けのファイルを新しく作る必要はないと書かれています(2026年11月に確かめました)。Google の AI の機能に載りたいから入れる、というものではありません。
Google には IndexNow で知らせても届きません。Google には、これまでどおりサイトマップ(Yoast SEO が出しているもの)で伝えています。
llms.txt を WordPress から返す
このサイトの llms.txt は、こんな形です(一部。URL のドメインは example.com に置きかえています)。
# オレオレナリ
> WordPress・Shopify のホームページ制作、保守、攻撃を受けたサイトの復旧を、システムエンジニアの上柳直人(NARI)が個人で受けています。個人だから安く、品質は本業と同じ。全国からオンラインで、ご相談は無料です。
## 主なページ
- [オレオレナリについて](https://example.com/about/): システムエンジニアの上柳直人(NARI)のプロフィールです。…
- [サービスと料金](https://example.com/service/): WordPress・Shopify のホームページ制作、公開後の保守、…
## 制作実績
一覧:https://example.com/works/
- [Shopifyデモストア 鳴々酒造](https://example.com/works/naripify-sake/): 架空の酒蔵「鳴々酒造」の Shopify ストアです。…
公開フォルダにファイルを置くのではなく、リクエストが来たら WordPress が作って返します。記事を公開すれば、すぐに一覧に載ります。
// /llms.txt を返す
add_action( 'parse_request', function ( $wp ) {
if ( 'llms.txt' !== $wp->request ) {
return;
}
nocache_headers(); // サーバーのキャッシュ(nginx)に古い一覧を残さない
header( 'Content-Type: text/plain; charset=utf-8' );
header( 'X-Robots-Tag: noindex', true ); // llms.txt そのものは検索結果に出さない
echo llms_txt(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- テキストとして返す
exit;
} );
parse_request は、WordPress がリクエストの URL を読み取ったあとに呼ばれるアクションです。$wp->request が llms.txt のときだけ、中身を出して終わります。
nocache_headers() を付けているのは、このサイトのサーバー(お名前.com のレンタルサーバー RS)の nginx のページキャッシュに、古い一覧が残らないようにするためです。
作った結果は覚えておき、記事を保存したら作り直す
毎回すべての記事を読んで作ると重いので、作った結果を1時間だけ覚えておきます(トランジェント)。記事を保存・削除したら、覚えていたものを消して作り直します。
// 記事を保存・削除したら作り直す
foreach ( array( 'save_post', 'deleted_post', 'transition_post_status' ) as $nari_base_hook ) {
add_action( $nari_base_hook, fn() => delete_transient( LLMS_TXT_TRANSIENT ) );
}
unset( $nari_base_hook );
説明文は Yoast SEO の meta description を使う
記事の一覧には、タイトル・URL と一緒に説明文を載せています。説明文は、Yoast SEO の説明文(meta description)を使い、なければ抜粋を使います。
function llms_txt_description( \WP_Post $post ): string {
$description = '';
if ( function_exists( 'YoastSEO' ) ) {
try {
// Yoast は記事ごとの情報を、自前のテーブル(yoast_indexable)の行の ID で覚える。
// 本番以外の環境では行を保存しないので ID が空になり、2 件目からも 1 件目の情報が返ってくる。毎回忘れさせてから読む
YoastSEO()->classes->get( \Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer::class )->clear();
$meta = YoastSEO()->meta->for_post( $post->ID );
$description = $meta ? (string) $meta->description : '';
} catch ( \Throwable $e ) {
$description = '';
}
}
if ( '' === $description ) {
$description = get_the_excerpt( $post );
}
$description = trim( preg_replace( '/\s+/u', ' ', wp_strip_all_tags( html_entity_decode( $description, ENT_QUOTES, 'UTF-8' ) ) ) );
return mb_strimwidth( $description, 0, 240, '…' );
}
Yoast SEO は、記事ごとの情報を自前のテーブルの行の ID で覚えています。本番以外の環境では、Yoast はその行を保存しないので ID が空になり、1件目の記事の情報が2件目以降にも返ってきていました。
そこで、記事ごとに Meta_Tags_Context_Memoizer の clear() を呼んで、覚えている情報を忘れさせてから読んでいます。
Yoast SEO の llms.txt を使わなかった理由
Yoast SEO にも、llms.txt を作る機能があります。ただ、公開フォルダに本物のファイルを書き出し、作り直すのは週に1回です。新しい記事をすぐに載せたかったので、Yoast の機能はオフに固定して、自分で作ることにしました。記事をあまり増やさないサイトなら、Yoast の機能で十分だと思います。
IndexNow で、公開・更新した URL を知らせる
IndexNow は、URL の一覧を JSON で送るだけのシンプルな仕組みです。送るときには「このサイトの持ち主である証明」として鍵を付け、同じ鍵を書いたファイルをサイトに置いておきます。検索エンジンは、そのファイルを読んで確かめます。
鍵を作って、/{鍵}.txt で返す
function indexnow_key(): string {
$key = get_option( 'nari_base_indexnow_key' );
if ( ! is_string( $key ) || ! preg_match( '/^[a-f0-9]{32}$/', $key ) ) {
$key = bin2hex( random_bytes( 16 ) );
update_option( 'nari_base_indexnow_key', $key, true );
}
return $key;
}
// 鍵のファイル(/{鍵}.txt)。ファイルは置かず、WordPress が返す
add_action( 'parse_request', function ( $wp ) {
if ( indexnow_key() . '.txt' !== $wp->request ) {
return;
}
header( 'Content-Type: text/plain; charset=utf-8' );
header( 'X-Robots-Tag: noindex', true );
echo indexnow_key(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 英数字だけ
exit;
} );
鍵は初めて使うときに作って、オプションに保存します。鍵のファイルも llms.txt と同じで、ファイルは置かずに WordPress が返しています。
送るのは本番の環境だけ
if ( 'production' !== wp_get_environment_type() ) {
return;
}
wp_get_environment_type() は、wp-config.php の WP_ENVIRONMENT_TYPE の値を返す関数です。ローカルでは local にしているので、ローカルで記事を公開しても送りません。
公開・更新を拾って、最後にまとめて1回だけ送る
add_action( 'transition_post_status', function ( $new_status, $old_status, $post ) {
if ( 'publish' !== $new_status || wp_is_post_revision( $post ) || wp_is_post_autosave( $post ) ) {
return;
}
if ( ! is_post_type_viewable( $post->post_type ) || 'attachment' === $post->post_type || '' !== $post->post_password ) {
return;
}
// Yoast の編集画面で「検索結果に表示しない」にした記事は知らせない
if ( '1' === get_post_meta( $post->ID, '_yoast_wpseo_meta-robots-noindex', true ) ) {
return;
}
indexnow_queue( get_permalink( $post ) );
// 新しく公開したときは、一覧ページ(/blog/ など)も変わるので一緒に知らせる
if ( 'publish' !== $old_status ) {
$archive = get_post_type_archive_link( $post->post_type );
if ( $archive ) {
indexnow_queue( $archive );
}
}
}, 10, 3 );
記事を公開・更新したときは、その URL をためておきます。新しく公開したときは、一覧ページ(/blog/ など)も中身が変わるので、一緒にためます。
// リクエストの最後に、まとめて 1 回だけ送る(返事は待たない)
add_action( 'shutdown', function () {
$urls = array_filter(
indexnow_queue(),
function ( $url ) {
$transient = 'nari_base_indexnow_' . md5( $url );
if ( get_transient( $transient ) ) {
return false;
}
set_transient( $transient, 1, 10 * MINUTE_IN_SECONDS );
return true;
}
);
if ( $urls ) {
indexnow_submit( $urls );
}
} );
ブロックエディターは、1回の保存で本文とメタボックスを別々に保存するので、何もしないと同じ URL を何度も送ってしまいます。なので、リクエストの最後(shutdown)にまとめて1回だけ送り、さらに同じ URL は10分に1回までにしています。返事は待たない(blocking を false)ので、保存が遅くなることもありません。
予約投稿の記事が公開されるときも、transition_post_status は呼ばれます。なので、予約した時刻に公開された記事も、そのまま IndexNow で知らせられます。
確かめ方
llms.txt は、ブラウザで開くか、curl で中身を見ます。
curl -s https://example.com/llms.txt
IndexNow は、送る関数の $blocking を true にして呼ぶと、返事を確かめられます。200 か 202 が返ってくれば、受け付けられています。
おわりに
llms.txt も IndexNow も、入れたからといって順位が上がるものではありません。特に llms.txt は、Google 検索では使われていませんし、どの AI がどこまで読んでいるのかもはっきりしません。なので、手間をかけすぎず、軽く入れておくくらいがちょうどいいと思っています。IndexNow のほうは、Bing などに新しい記事を早く知らせられるので、入れておいて損はないです。
どちらも PHP だけで、それぞれ200行足らずで作れました。配布のプラグインを増やさずに済むので、ほかの案件にも同じものを入れやすいです。
WordPress の SEO まわりのカスタマイズのような制作・改修のご相談も受け付けています。お気軽にどうぞ。