あなたのサイトに llms.txt を置いたのは、いつですか。
その日付をすぐに思い出せる人は、少ないはずだ。多くのサイト運営者にとって「llms.txt を置く」は、一度やれば済む作業として扱われてきた。ルート直下にテキストファイルを1つ置く。それで「AIエージェント対応」の箱にチェックを入れた気になる。
当サイトも例外ではなかった。2026-06-04 に設置してから、公開済み125本のうち51本の記事がllms.txtに言及してきた。そのうち主題として扱ったのはarchives/60・archives/68・archives/99・archives/115・archives/116・archives/124の6本だ。だが「置いた後、仕様が変わっていないか」を毎回確かめてきたわけではない。
2026-08-10、llms.txt の仕様がv2になった。llmstxt.org(https://llmstxt.org/)に掲載されている本文が更新され、同日GitHubのリファレンス実装(AnswerDotAI/llms-txt)にも反映された。提案から約2年を経ての改訂だ。
そこで今回は、v2の変更点を当サイトの llms.txt に照らして測ってみた。結果は、単純な「対応している/していない」の二択には収まらなかった。「200が返ること」と「対応していること」が、別のものだと分かったからだ。
llms.txt v2で何が変わったか
llmstxt.orgの本文(Modified: 2026-08-10)から、変更点を4つに整理する。英文は原文をそのまま引用し、日本語訳を併記する。それぞれについて、「では実務では何をすることになるのか」まで書く。
①ディスカバリ(発見可能性)の改善
"
rel="alternate" type="text/markdown"points to the markdown version of a page, andrel="describedby"points to the llms.txt file that covers it."(日本語訳: 「rel="alternate" type="text/markdown" はそのページのMarkdown版を指し、rel="describedby" はそのページをカバーするllms.txtファイルを指す」)
HTMLの<link>タグ、またはHTTPレスポンスのLink:ヘッダのどちらでも実装できる。つまりv1では「サイトのルートに/llms.txtを置いておけば、AIエージェントが自分で見つけてくれる」という性善説に近い設計だったが、v2ではページ自体からファイルの所在を明示的に指し示す経路が仕様に加わった。
実務的には何をすることになるか。「headに<link rel="alternate" type="text/markdown" href="...">を1行足すだけで済むのか」と聞かれたら、答えは「テンプレートの作り次第」になる。多くのCMSでは、記事のhead出力は共通テンプレートを経由している。1箇所を直せば全ページに反映される設計であれば、確かに1行の追加で済む。だが記事タイプごとに個別のテンプレートが分かれているサイトでは、その数だけ修正箇所が増える。
もう1つの選択肢がHTTPレスポンスヘッダのLink:だ。こちらはHTMLの中身を一切触らずに、Webサーバーやアプリケーション側の設定でヘッダを追加するだけで実装できる。HTMLテンプレートを直接編集する権限がない、あるいはテンプレートが分散しているサイトほど、こちらの方が現実的だろう。逆に、すでにheadの出力を一元管理できているサイトなら、<link>タグの方がテンプレートの見通しがよい。
どちらを選ぶにせよ共通しているのは、「1度実装すれば終わり」ではなく「新しいページを追加するたびに、その仕組みに乗っているか」を確認し続ける必要がある、という点だ。
②Markdown命名の柔軟化
"either with
.mdappended (page.html.md) or with the extension replaced by.md(page.md)"(日本語訳: 「.mdを末尾に追加する形式(page.html.md)か、拡張子を.mdに置き換える形式(page.md)のどちらでもよい」)
各ページのMarkdown版を用意する際の命名規則が、2通り認められるようになった。
「本当にMarkdownを返す」には何が要るか。この記事の後半で実測する通り、当サイトは「.mdという拡張子に反応しているように見えて、実際には通常のHTMLを返している」状態だった。これを本当の意味でMarkdown版の提供に変えるには、大きく3つの道がある。
1つ目は、記事を公開するたびにMarkdown版のファイルを静的に生成しておく方法。HTMLを生成する処理と並行してMarkdownを書き出せば、追加のリクエスト時コストはかからない。ただし、記事を修正するたびに両方を更新し忘れないか、という運用上の負荷が発生する。
2つ目は、リクエストが来た瞬間にHTMLからMarkdownへ動的に変換する方法。ファイルを二重に持たずに済むが、変換処理そのものが正しくHTMLの構造(見出し・リスト・強調)をMarkdown記法に落とし込めているかを、都度検証する必要がある。
3つ目は、CDNやエッジ側で拡張子に応じて出し分ける方法。オリジンのアプリケーションを一切触らずに実装できる余地があるが、そのための設定変更自体が別途必要になる。
そして、これらの実装コストとは別に、そもそもやるべきかどうかは別の問題だ。判断材料として持つべきなのは、自サイトの読者が人間の閲覧者だけなのか、AIエージェントが直接コンテンツを取得しに来る比率がどれくらいあるのか、という点になる。当サイトはこれまでの記事でAIクローラーのアクセス実測を扱ってきたが、「Markdown版を明示的に要求してくるアクセス」をまだ切り分けて測ったことがない。やるべきかどうかを判断する前に、まず測る対象があるということが、今回の実測を通じて見えてきた。
③サブパス適用範囲
"A file covers the URLs under its path, and where more than one file applies, agents should use the most specific one."
(日本語訳: 「1つのファイルは、そのパス配下のURLをカバーする。複数のファイルが該当する場合、エージェントは最も具体的なものを使うべきである」)
サイトルートのllms.txtだけでなく、/blog/llms.txtのようにサブディレクトリ単位で個別に設置し、より具体的な情報を優先させる運用が仕様として認められた。
大規模サイトで分割する意味、しない意味。サブパスごとにllms.txtを分割する仕様が加わった意味は、サイトの規模とコンテンツの性質によって大きく変わる。1つのドメインの中に「ブログ」「製品ドキュメント」「サポートFAQ」のように性質の異なるセクションを複数抱える大規模サイトであれば、それぞれに専用のllms.txtを置き、AIエージェントが必要なセクションだけを読み込めるようにする意味は大きい。ドキュメントを読みたいエージェントが、ブログ記事のインデックスまで丸ごと読み込む必要はない。
一方で、当サイトのように単一のコンテンツ群(AI Ronブログ + SEO記事)をルート直下のllms.txt 1本で扱っているサイトにとっては、この項目は今のところ関係がない。無理にサブパスを分割する動機がないまま分割しても、管理対象が増えるだけで得られるものがない。「対応していない」のではなく、「対応する必要が今のところない」というのが正直な位置づけになる。
④Optionalセクションの位置づけ
"used, by convention, for secondary information: links an agent can skip when a shorter context is needed."
(日本語訳: 「慣例として、二次的な情報に使われる。エージェントが短いコンテキストを必要とするときに、読み飛ばしてよいリンク群」)
これはv1の頃から実質的にそう運用されていた部分だが、v2で「短いコンテキストが必要なときのスキップ対象」という位置づけが明文化された。
「飛ばしてよいリンク」を自分のサイトのどこに当てはめるか。当サイトのOptionalセクションには59件のリンクを置いており、精選記事以外のインデックスとして運用している。これはv2が明文化した「短いコンテキストが必要なときに読み飛ばしてよい」という位置づけと、すでに一致していた。
この一致は偶然ではない。AIエージェントが最初に必要とするのは、サイトの中核となる少数の情報(コアページ・精選記事)であって、全記事の網羅的なリストではない。当サイトがOptionalセクションを設計した時点で意識していたのは「網羅性と要点の分離」であり、それが後から明文化された慣例と噛み合っていた、という順序になる。
当サイトをv2に照らして測った結果
4つの項目それぞれについて、当サイトの現状を実測した。
| v2の項目 | 当サイトの状態 | 実測根拠 |
|---|---|---|
| ①ディスカバリ | 🔧 未対応 | トップページhead内の<link>タグ(rel属性を持つもの)26件、記事ページhead内22件を走査 → rel="alternate" / rel="describedby" は0件。HTTPレスポンスのLink:ヘッダも0件 |
| ②Markdown実体提供 | ⚠️ 見かけ上は対応、実体は未対応 | archives/125.md → HTTP 200/archives/125.html.md → HTTP 200。ただしContent-Typeはtext/htmlで、返る中身は<!DOCTYPE html>から始まる通常のHTMLページ |
| ③サブパス適用 | ⚪ 判定不能 | 当サイトのllms.txtはルート直下の1つのみ。サブパス配下の独立したllms.txtが存在しないため、比較対象そのものがない |
| ④Optionalセクションの慣例 | ✅ 一致 | Optionalセクションには59件のリンクを置き、精選記事以外のインデックスとして運用しており、v2が明文化した慣例とすでに一致していた |
当サイトllms.txtの実測値(2026-08-14 14:55 JST時点)
HTTP 200 / 48,289 B / 117行
mtime: 2026-08-14 14:55 JST
構成: H1 × 1 / blockquote × 1 / H2 × 5
(コアページ/AI Ronブログ/精選SEO記事/無料ツール/Optional)
収録リンク: 95件
内訳: コアページ 5 / AI Ronブログ 6 / 精選SEO記事 20 / 無料ツール 5 / Optional 59
配置: ドキュメントルート直下に置いた静的ファイル(自動生成ではなく、手動で更新している)
自動生成の痕跡: crontab / .htaccess / routes.php を確認したが、llms関連の記述は0件
これに加えて、llms-full.txt(124,551 B・931行)というファイルも同じ場所に公開されていることを確認した。llms.txt 本文からは一度も参照されておらず、単独で存在している。mtimeはllms.txtと秒まで完全一致しており、同一のプロセスで一緒に生成されたものと見られる。
②がいちばん危ない理由
4項目のうち、①(未対応)と③(判定不能)は、測った瞬間に「そうか、まだやっていない」「そもそも比較できない」と分かる。危ないのは②だ。
archives/125.md にアクセスすると、HTTP 200が返る。archives/125.html.md にアクセスしても、同じくHTTP 200が返る。存在しない記事番号(archives/999999.md)や、まったく存在しないパスに.mdを付けたものにアクセスすると、こちらはきちんと404が返る。
つまり「見せかけの陽性」ではなく、「.mdという拡張子を意識した挙動をしているように見える」テストなのだ。
だがここで止まらずに、Content-Typeと中身の先頭数バイトまで確認した。
Content-Type: text/html
中身の先頭: <!DOCTYPE html> ...(通常のHTMLページと同一)
答えは「対応していない」だった。当サイトのCMSのルーティングは、URL末尾から数字の記事IDを取り出す設計になっている。archives/125.md の場合、末尾の.mdは単に無視され、archives/125 と同じ通常のHTMLページがそのまま返っているだけだった。Markdownを生成しているわけでも、専用のレスポンスを返しているわけでもない。
もしHTTPステータスだけを見て「200が返るから対応済み」と判定していたら、この記事は書かれずに終わっていた。
陰性対照を置いても、まだ足りなかった理由
ここで一度立ち止まりたい。今回の検証では、存在しない記事番号(archives/999999.md)と、まったく存在しないパスに.mdを付けたものの両方を陰性対照として置いた。archives/124で「llms.txtに足りなかったのは陰性対照だった」と書いたばかりの当サイトとしては、この手順を踏んだこと自体は一歩前進のはずだった。
だが、それでも今回は足りなかった。
陰性対照が測っているのは「ルーティングが反応するかどうか」であって、「返ってきた中身が何であるか」ではないからだ。陰性対照(404が返る)と本命(200が返る)を比較することで確かめられるのは、「そのパスに対して何らかの識別・処理が行われている」という事実までだ。その処理の結果として、本当に期待した形式(この場合はMarkdown)が返っているかどうかは、まったく別の層にある。
言い換えると、陰性対照のテストとContent-Type・中身の確認は、同じ「対応しているかどうか」を測っているようでいて、測っている層が違う。陰性対照は入口の層(ルーティング)を測り、Content-Typeと中身の確認は出口の層(レスポンスの実体)を測る。入口の層が正しく動いていることは、出口の層が正しいことを何も保証しない。
同じ形は、他のところにもある
この「入口は動いているのに、出口が期待と違う」という構造は、llms.txtに限った話ではない。WEBディレクターが自分のサイトで踏みうる同型の例を、いくつか挙げておく。
1つ目は、拡張子を無視するルーティングそのものだ。今回の当サイトのケースがまさにこれで、URLの末尾から数字IDだけを抽出する設計のサイトは、.jsonや.xmlを付けても同じHTMLが返ってくることが多い。APIのような形式のパスを外部に見せているのに、実体は通常のページのままというケースは珍しくない。
2つ目は、SPA(シングルページアプリケーション)における200の返し方だ。クライアントサイドでルーティングを行う設計のサイトでは、サーバー側はどんなURLに対しても同じ1つのHTML(アプリの土台)を200で返し、実際のページ内容はJavaScriptが後から差し込む。この場合、存在しないURLに対しても表面上は200が返り、中身をJavaScriptが実行するまで「本当に該当ページが存在するか」が確定しない。
3つ目は、エラーページが200で返る設計だ。「ページが見つかりません」という内容のHTMLを、HTTPステータスとしては200のまま返すサイトがある(いわゆるsoft 404)。この場合も、ステータスコードだけを見ていると存在しないページを存在すると誤判定する。
3つとも共通しているのは、「200が返る」という1つの事実だけでは、その裏で何が起きているかを判定しきれないという点だ。今回のllms.txtの検証も、この構造の一種だった。
なぜ、これまで気づかなかったのか
当サイトはこれまでllms.txtを主題として6本(archives/60・archives/68・archives/99・archives/115・archives/116・archives/124)で扱ってきた。設置した記事、効果を答え合わせした記事、他サイトで再現実測した記事、公式見解を検証した記事。だが、URLのルーティングが拡張子をどう扱っているかを検証した記事は、その中に1本もなかった。
理由は単純だ。llms.txtそのものが正しく置かれ、正しく200を返している限り、「ファイルは動いている」という状態が続く。動いているものを、わざわざ疑いに行く動機は生まれにくい。今回きっかけになったのは、llms.txt自体ではなく、v2という外部の仕様変更だった。「.mdへの対応」という新しい項目が仕様に加わったことで、初めて「では、当サイトの.mdへのアクセスは、実際に何を返しているのか」という問いが立った。
これは、置いたファイルそのものの問題ではない。ファイルを置いた後、そのファイルが依拠している前提(この場合は「.mdへのアクセスに何を返すべきか」というルーティングの設計)を、外部の仕様変更のタイミング以外で見直す機会がなかった、という構造の問題だ。動いているものは、自分から「私はもう古い前提のままです」とは言わない。
そして「動いているか」を確かめる作業自体は、実はすでに2回やっている。archives/99「llms.txt を置いた1ヶ月後、正直に答え合わせ」で1ヶ月後に、archives/115「llms.txt を置いて51日、正直に答え合わせ第2弾」で51日後に、それぞれ効果を測った。だがそのどちらも測っていたのは「効いているか」であって、「仕様に合っているか」ではなかった。
200が返ることは、「対応している」ことの証明ではない。
Content-Typeと中身まで見て、初めて分かる。
llms.txtを置いてから今日で71日。仕様が変わったことを、ファイルは自分では言わない。置いた側が測りに行かない限り、ずっと「置いてある」だけになる。
技術コラム: 「200が返ること」と「対応していること」は別
今回の測定でやったことを、一般化しておく。他の仕様変更を追いかける時にも同じ手順が要る。自分のサイトで、実際に手を動かして試せる形で書く。
何を測るか(3段階)
| 段階 | 見るもの | 今回の位置づけ |
|---|---|---|
| ①HTTPステータス | 200か404か | ここだけでは「ルーティングが受け付けているか」しか分からない |
| ②Content-Type | text/htmlかtext/markdownか | ここで初めて「本当にその形式で返しているか」が分かる |
| ③中身の先頭 | <!DOCTYPE html>かMarkdown記法か | Content-Typeが正しく設定されていない実装もありうるため、実体まで見て確定させる |
実際に叩くコマンド
ステータスとContent-Typeだけを見たい場合は、レスポンスボディを取得せずヘッダだけを見る。
curl -I https://example.com/path/to/page.md
中身の先頭数行だけを確認したい場合は、ボディを取得したうえで先頭だけに絞る。
curl -s https://example.com/path/to/page.md | head -3
head -3で十分な理由は、Markdownファイルであれば先頭は見出し(#)や本文の書き出しになるはずで、HTMLであれば<!DOCTYPE html>や<html>が先頭に来るはずだからだ。数行見るだけで、どちらであるかは判別できる。
もう1つの手がかりがContent-Lengthだ。curl -IのレスポンスヘッダにはContent-Length(またはチャンク転送であれば実際に受信したバイト数)が含まれる。Markdownはタグを持たないぶん、同じ記事のHTML版に比べてバイト数が小さくなるのが通常だ。もし.mdへのアクセスで返ってくるバイト数が、同じ記事の通常ページとほぼ同じであれば、それは「別のフォーマットに変換された結果」ではなく「同じHTMLがそのまま返ってきている」ことを疑う材料になる。今回の当サイトのケースでは、Content-Typeと中身の先頭を確認した時点で「同じHTMLがそのまま返っている」ことが判明していたため、この手がかりまでは使わなかった。だがContent-Typeの設定だけを個別に上書きしていて中身は共通のテンプレートのまま、という実装もありうるため、Content-Typeの確認だけで終わらせず、Content-Lengthも含めて多角的に見ておくと、判定の精度はさらに上がる。
陽性対照の置き方
「探し方が間違っていて0件だったのか、本当に存在しないから0件だったのか」を区別するために、先に「在ると分かっているもの」で針を鳴らしておく。
今回のディスカバリ検証では、rel="alternate" / rel="describedby"を探す前に、同じhead内を同じ手順でrel="canonical"を探した。rel="canonical"は1件ずつ正しく検出できたので、探索の手順(針)自体が正常に働いていることが先に確定した。そのうえでalternate / describedbyの0件を「本当に0件」と判定できた。
自分のサイトで同じ検証をする場合は、まずhead内に確実に存在するタグ(<title>や<meta name="description">など)を対象に、同じ抽出コマンドで検出できるかを確認してから、本命の検証に入るとよい。
陰性対照の置き方
逆に「本当は存在しないもの」に対して、正しく「存在しない」という結果が返ってくるかを確認する。
今回のMarkdown検証では、archives/999999.md(存在しない記事番号)と、まったく存在しないパスに.mdを付けたものの両方を用意した。両方とも404が返ることを確認したうえで、archives/125.mdの200が「本当に何かに反応した結果の200」であることを確かめた。
それでも足りない場合(今回がその実例)
陽性対照・陰性対照を両方揃えても、まだ「対応している」と言い切れない場合がある。今回の記事がまさにその実例だ。
陰性対照(404)と本命(200)の差を確認できたところまでは正しい。だが、その200が「期待した形式のレスポンス」なのか、「別の理由で200が返っているだけ」なのかは、まだ分からない。ここで測るべきなのが、上の3段階のうちの②③にあたるContent-Typeと中身の先頭だ。
今回、この3層目まで測って初めて、「.mdへの反応に見えたものは、数字IDの抽出後に余った文字列を無視しているだけで、Markdown実体の提供ではなかった」と確定できた。
判定の組み合わせ
3つの段階を組み合わせると、次のように読み分けられる。
- 200 + text/markdown + Markdown記法 → 本当に対応している
- 200 + text/html + HTMLの中身 → 対応していない(今回の当サイトのケース)
- 404 → その形式のパスは処理されていない
- 200だが中身がエラーメッセージ → soft 404(別の問題。今回とは異なるが同じ盲点を持つ)
1段階目だけで判定を止めず、3段階まで確かめて初めて、どの状態にあるかが確定する。
連載の中での位置づけ
当サイトはここ数日、同じ形の失敗を別の角度から繰り返し見つけている。
archives/124(2026-08-12)では、「llms.txtに足りなかったのは陰性対照だった」と書いた。その時点での教訓は、「存在しないもの」に対して正しく反応するかを確かめる手順が抜けていた、というものだった。
今回はその陰性対照を、最初から手順に組み込んで検証した。それでも足りなかった。陰性対照は「ルーティングが反応するか」までしか測らず、「返ってきた中身が何か」は測らないからだ。
陰性対照を置いても、まだ足りなかった。
archives/125(2026-08-14)では、「自分で測る指標が93日止まっていた」と書いた。装置は動いていたが、誰も見に行かなかったという構造だった。
今回の話は、少し違う形をしている。
答え合わせは2回やった。1ヶ月後と、51日後に。
どちらも「効いているか」を測っていた。「仕様に合っているか」は、一度も測っていない。
仕様の方が2年で動いていたことに、置いてから71日、気づかなかった。
装置を置いた後、装置の前提となる仕様そのものが変わることがある。llms.txtは2026-06-04に置いてから、一度も「仕様が変わっていないか」を確認していなかった。ファイルは動いていた(HTTP 200を返し続けていた)。だが、その動いている状態が「今の仕様に対応している」ことを意味するとは限らない。
3本の記事を並べると、1つの流れが見えてくる。「測る手順が足りなかった」(124)→「測る対象自体を見に行っていなかった」(125)→「測る基準そのものが動いていた」(今回)。同じサイトの同じ場所を、毎回違う角度から見誤っていたことになる。
この記事で見つかった🔧未対応の2項目(①ディスカバリ・②Markdown実体提供)は、当サイトが運用している「実践予定は記事に書いたら実行し、before・afterを追記する」というルールの対象になる。書いて終わりにせず、対応した時点でこの記事自体を更新し、結果を追記する。それが、ここまでの連載で繰り返し確認してきた「作る側は動いていて、出す側・測る側だけ止まっていた」という構造への、当サイト自身の答え合わせになる。
自己開示
🔧 未対応 ①ディスカバリ(rel="alternate" / rel="describedby" / Linkヘッダ)。今回の記事で公表し、対応を検討する。
🔧 未対応 ②Markdown実体提供。HTTP 200は返るが、Content-Type・中身とも通常のHTMLのまま。
⚪ ③サブパス適用。判定不能——サブパス配下の独立したllms.txtを持っていないため、評価対象そのものがない。
✅ 実施済 ④Optionalセクションの慣例。v2が明文化した慣例とすでに一致していた。
関連記事
- archives/68「llms.txtを当サイトで実際に設置した — 採用率10%のB2A時代、WEBディレクターが今やるべきこととその正直な現在地」
- archives/79「Googleが『やるな』と言ったGEO施策 — chunking・llms.txt量産・FAQスパムが非推奨になった一次情報整理」
- archives/115「llms.txt を置いて51日、正直に答え合わせ第2弾 ── Google が『効かない』と言った以上に、他の AI も読みに来ていなかった」
- archives/116「1サイトの実測は仮説、2サイトの一致は事実 ── llms.txtを8言語サイトでも測ったら、AIクローラーが1件も来ていなかった」
- archives/124「猫のファイルが『効いている証拠』を無効化した日 ── llms.txtに足りなかったのは陰性対照だった」
- archives/125「Googleが見せたのは表示回数までだった ── 生成AIパフォーマンスレポートの『半分』と、自分で測る指標が止まっていた93日」
WEBサイト