自作の小さな Web サーバで、公開ページの応答に Referrer-Policy: no-referrer を付けていました(2026-08-05 から)。
外へのリンクを踏まれたときに、内部の URL を相手に渡さないためです。
(正確には、404 の応答にだけこのヘッダが抜けており、そこが埋まったのは 2026-08-30 です。
下で扱う7件はすべて実在するページ=200 の応答なので、この抜けは7件の説明には効きません)
アクセスの記録は、要求の Referer からホスト名だけを取り、日ごとに数えています。
2026-09-11 までの記録にある参照元は、次のとおりです(記録は 2026-08-06 から)。
| 参照元 | 件数 |
|---|---|
| なし(記録上は「直接」) | 108 |
| 自分のサイトのホスト | 7 |
| qiita.com | 2 |
自分のホストが 7件 あります。下の実測のとおり Chrome ならサイト内を移動しても Referer は付かないので、ここは 0 件になるはずでした。
7件は 2026-08-20〜08-22 の3日間(2件・2件・3件)にしかありません。
再現: 1つのローカルサーバで、受け取ったヘッダだけを記録する
127.0.0.1 で立てたサーバが、実際に受け取った要求の Referer を記録します(送る前に横取りはしません)。
ページ a は読み込まれるとすぐ、ページ内の click() で <a> のリンク先 b へ移ります。
別オリジンは 127.0.0.1 と localhost の違いで作りました(ポートは同じでもホストが違えば別オリジンです)。
Chrome は画面なし(--headless=new --dump-dom)で1ページずつ開きました(Windows 11 10.0.26200 / Node v24.18.1)。
| a の応答の Referrer-Policy | 移動先 | b が受け取った Referer |
|---|---|---|
| 付けない(Chrome の既定) | 同じオリジン | http://127.0.0.1:8931/default/a.html |
| 付けない(Chrome の既定) | 別オリジン |
http://127.0.0.1:8931/(オリジンだけ) |
no-referrer |
同じオリジン | 無し |
no-referrer |
別オリジン | 無し |
same-origin |
同じオリジン | http://127.0.0.1:8931/same-origin/a.html |
same-origin |
別オリジン | 無し |
1行目が陽性対照です(同じ作りで Referer が届く)。これがあるので「無し」を「測れていない」と区別できます。
6通りとも、返ってきた DOM に b のページの <title>b</title> があることを確かめています(移動そのものは起きています)。
もう1つ、Node の fetch で a を取り(応答に Referrer-Policy: no-referrer が付いていることを確認)、
続けて 自分で Referer: http://127.0.0.1:8931/no-referrer/a.html を付けて b を取りました。
b が受け取った Referer は、その付けた値のままでした。
分かったこと
1. 応答のポリシーは、要求を自分で組み立てるクライアントを縛らない
要求に自分で Referer を付ければ、その値はそのままサーバに届きました(Node の fetch はこのヘッダを落としませんでした)。
なお ブラウザの fetch() では Referer は禁止ヘッダ名なので、同じことはできません。
この実測が言えるのはここまでで、「fetch がポリシーを読んで無視した」とは言えません(ポリシーを適用させる場面を1つも作っていないからです)。
冒頭の7件は、この形なら説明がつきます。
記録の順に並べると、2026-08-19 に robots.txt と記事一覧のページを参照元なしで取り、
08-20〜08-22 に 1日2〜3件ずつ、それより前に取ったページの上にあるリンク先 を取っていました。
リンク構造は当時のコミットに固定して機械で突き合わせ、7件とも直前までに取ったページのリンクで説明できました(7/7)。
ただし 1件は、記事一覧のページが自分自身へ張っているリンクで説明したもので、他の6件と同じ重さではありません。
正体は言えません。ユーザーエージェントを保存していないからです。
また記録は日単位の件数なので、同じ日の中の順番は、どのページも件数が1件の日にかぎり、キーの並びを最初に触った順として読めます(この3日間はそうでした)。
2. Chrome では、サイト内の移動でも Referer が消え「直接」に落ちた
表の3行目のとおり、no-referrer は同じオリジンへの移動からも Referer を消します。
つまり冒頭の「直接 108件」には、外から URL を直接開いた人と、自分のサイトの中でページを移っただけの人が、区別できない形で混ざっています。
「自分のホストからの遷移 7件」は、サイト内を移った人の数ではありません。
外へ内部の URL を渡さないことが目的なら、Chrome で <a> を押しての移動について測った範囲では same-origin で足りました(表の5・6行目)。
同じオリジンには付き、別オリジンには付きません。
Chrome の既定(何も付けない)でも、別オリジンにはオリジンだけが渡り、パスは渡りませんでした(表の2行目)。
どれを選ぶかは、何を外に出したくないか次第です。
ただし切り替えると、その日からサイト内の移動が記録に現れるので、それ以前の「直接」の数とは比べられなくなります。
確かめ方
依存パッケージは要りません。私は Chrome と Node v24.18.1 で、次の形の1ファイル(89行)を走らせました。
下のコードは実ファイルの29〜33行目をそのまま貼り、以降を ... で省略しました(// a.html は… の1行だけは説明のために足しています)。
// 抜粋(サーバ側)。case は測定ケース名、host は別オリジンかどうかの根拠
const server = http.createServer((req, res) => {
const [, policy, file] = req.url.split("/");
received.push({ case: currentCase, host: req.headers.host, path: req.url, referer: req.headers["referer"] ?? null });
const headers = { "Content-Type": "text/html; charset=utf-8" };
if (policy === "no-referrer" || policy === "same-origin") headers["Referrer-Policy"] = policy;
// a.html はリンクを1本持ち、読み込まれると click() で移る
...
});
- a.html に
<a id="next" href="/no-referrer/b.html">とdocument.getElementById("next").click()を置く chrome --headless=new --user-data-dir=<空のフォルダ> --no-first-run --virtual-time-budget=3000 --dump-dom http://127.0.0.1:8931/no-referrer/a.html- サーバが b.html の要求で受け取った
refererを見る
言えないこと
- 実測は Chrome 1種類・このPC 1台 です。インストール先に版のフォルダが2つあり(151.0.7922.171 / 152.0.7977.83)、どちらが走ったかは確かめていません。Firefox・Safari は見ていません
- 移動はページ内の
click()で、人がマウスで押したものではありません。測ったのは<a>での移動だけで、画像などの副リソース・ダウンロード・fetch/XHR・フォーム送信・リダイレクトは1件も測っていません - 測ったのは http → http の loopback 間だけです。既定の
strict-origin-when-cross-originには HTTPS のページから HTTP へ渡すときにRefererを丸ごと落とす分岐がありますが、そこは一度も通していません -
--virtual-time-budgetで打ち切るので、ページの読み込み後に出る付随要求(favicon など)は毎回そろうとは限りません。実際、6通りのうち1通りだけ favicon の要求が届いていません(上の表の結論は、b のページに着いたことで確かめています) - 冒頭の7件の正体(人か機械か、何のクライアントか)は分かりません
- 「直接 108件」のうち何件がサイト内の移動だったかは、この記録からは分けられません
この件を記録していたサーバで、Windows で AI を無人で走らせるときの落とし穴を点検する道具を配っています(無料・依存なし・読むだけで書き換えません・通信しません):