きっかけ:Xポストを差し込んだら、読者が外に飛んだまま戻ってこない構造になっていた
Micron SOCAMM2 容量半減レポートの解説記事を書いていて、Dylan Patel 本人のXポストを Section 4 に引用した。最初は素直にマークダウンリンクで書いた。
[元ポスト(@dylan522p)](https://x.com/dylan522p/status/...)
dev サーバーで描画を確認したあと、念のためリンクをクリックしてみたら、ブラウザの今開いているタブの URL が x.com/... に書き換わり、自分のブログ記事の本文が一瞬で消えた。Xに遷移したあと「戻る」を押せば戻れるが、読者はわざわざ戻ってこない。本文を読みかけのままタブを閉じる確率が高い。
長文の解説記事ほど、本文の途中に外部ソースへのリンクを差し込みたくなる。マークダウンの素直な書き方をそのまま使うと、ソースを示すたびに読者が本文から離脱する構造になってしまう。
別タブで開かせたい、と Claude Code に指示を出した。
段階的に分かった問題:HTMLアンカーに書き換えたら、リンクが入れ子で2回光った
target="_blank" を付けるには、マークダウンの [label](url) 記法では足りない。GitHub Flavored Markdown 自体に target 属性を指定する構文がない。仕方なく HTML アンカーを直書きする。
<a href="https://x.com/dylan522p/status/..." target="_blank" rel="noopener noreferrer">https://x.com/dylan522p/status/...</a>
書き換えて dev サーバーをリロードしたら、画面の Section 4 で同じ URL が青く2回続けて光った。よく見ると、リンクテキストの URL が、HTMLアンカーの外側でさらに別の <a> タグに包まれている。DevTools で要素を覗くと、<a target="_blank"> の中にもう一段 <a> が入り、リンクが入れ子になっていた。
原因は @nuxt/content の GFM autolinker だった。ベアの URL 文字列を見つけると、自動で <a> に変換する機能。マークダウンで https://example.com と書くだけでリンクになる、あの便利な挙動。今回は、自分で書いた HTML アンカーの中身がたまたまベアURLの文字列だったため、autolinker がそれを検出して二重にリンク化していた。
リンクテキストを URL でないラベルに変えた。
<a href="https://x.com/dylan522p/status/..." target="_blank" rel="noopener noreferrer">→ 元ポスト(@dylan522p, 2026-06-04)</a>
リロードすると、青く光るリンクが1回だけになった。クリックすると別タブで Xが開き、本文ページはそのまま残った。Meritz Securities のレポート出典も同じパターンで「Meritz Securities Research Center」というラベルに差し替えた。
永続化の判断:明日また同じことを忘れる
この種の罠は、記事を書くたびに踏みやすい。普通のマークダウンリンクは反射で書いてしまうし、HTMLアンカーに書き換えるときも「URLが見えた方が親切かな」と思ってリンクテキストに URL を入れがち。今日気づいた知見をまた検索し直すのは時間の無駄なので、その場でルールを書かせた。
.claude/rules/external-link-target-blank.md に外部リンクの書き方を固定し、プロジェクトルートの CLAUDE.md の「ルール所在マップ」表に1行追加した。Claude Code は親ディレクトリの CLAUDE.md を読みに行くので、マップから辿れば自動的にルール本体に到達する。
コミットも論理単位で3本に分けた。①Micron 記事本体、②ルール本体+マップ追記、③表示確認のスクショ。ルールだけ独立コミットにしておくと、後から git log .claude/rules/ で「いつ・なぜ追加されたか」を辿れる。
ルールの中身(要点)
.claude/rules/external-link-target-blank.md の要点だけ抜き出す。
外部リンクの定義: 当ドメイン以外(x.com、github.com、semianalysis.com、ニュースサイト、論文、Wikipedia、メーカー公式、その他全て)。
NG: 普通のマークダウンリンク
[元ポスト](https://x.com/dylan522p/status/...)
同タブで開いてしまう。
NG: ベアURLの autolink
https://x.com/dylan522p/status/...
GFM autolinker が <a> に変換するが、これも同タブ。
OK: HTMLアンカーで明示
<a href="https://x.com/dylan522p/status/..." target="_blank" rel="noopener noreferrer">→ 元ポスト(@dylan522p, 2026-06-04)</a>
ポイントは2つ:
target="_blank"で別タブ、rel="noopener noreferrer"でリンク先からのwindow.opener参照と referrer 漏洩を遮断する- アンカーテキストに URL そのものを書かない。書くと GFM autolinker が中身をさらに
<a>で包み、<a><a>...</a></a>の入れ子になって描画が崩れる。「→ 元ポスト」「Meritz Securities Research Center」のような URL でないラベルにする
内部リンクはマークダウンのまま
[CUDA入門](/cuda-programming-child-friendly-guide)
同サイト内遷移はSPAで切り替わるので、別タブにすると逆に読者の体験を壊す。<NuxtLink> 相当の動作で同タブ遷移させる。
今日の学び:GFM autolinker は便利だが、HTMLアンカーと混ぜるなら中身に URL を入れない
3つに言い換えられる学び。
- マークダウンの便利機能(GFM autolinker)と HTML 直書きは、意外なところで衝突する。両方が「URL を見つけたらリンク化する」仕事を持っているので、同じ URL文字列に両方が手を出すと入れ子になる
- リンクテキストは URL でない言葉にした方が読者にも親切になる。「https://x.com/dylan522p/status/1830...」よりも「→ 元ポスト(@dylan522p, 2026-06-04)」の方が文脈が伝わる
- 一度踏んだ罠は、忘れる前に
.claude/rules/に書いてCLAUDE.mdのマップから参照を張る。次の記事を書き始めたとき、自分は罠の存在を覚えていない前提で設計する
ルールができたので、明日以降は外部リンクを書くたびに、Claude Code がこのルールを参照してアンカー形式で書いてくれるはず。覚えておく仕事を .claude/rules/ に外注した。