投稿フォームやコメント欄のテキストに含まれるURLを、自動でクリックできるリンク(aタグ)に変換したい場面はよくあります。JavaScriptで実装できますが、やり方を誤るとXSS(クロスサイトスクリプティング)の穴になるため、安全な手順を押さえることが重要です。
この記事の結論(安全な手順):①先にHTMLエスケープしてXSSを防ぐ → ②正規表現でURLを検出(HTMLの危険文字や句読点は含めない) → ③
target="_blank" と rel="noopener noreferrer" を付けてリンク化。本番では実績のあるライブラリを使うのも有力です。まずNG例:そのまま innerHTML に入れると危険
よく見かける素朴な実装には、重大な問題があります。
NG: XSSと誤変換を起こす書き方
function replaceURLWithLink(text) {
const urlRegex = /(https?:\/\/[^\s]+)/g; // [^\s]+ が " も飲み込む
return text.replace(urlRegex, (url) => `<a href="${url}">${url}</a>`);
}
const div = document.getElementById("content");
div.innerHTML = replaceURLWithLink(div.innerHTML); // エスケープなしで危険
何が危険か:
[^\s]+ は空白以外をすべて飲み込むため、https://x.com"onmouseover="alert(1) のような文字列でonmouseover 属性が注入されてXSSが発火します。さらに https://example.com。 の「。」や (https://example.com) の「)」までリンクに含まれ、innerHTML を読み直す処理は既存の <a> を壊します。安全な実装:エスケープ → 検出 → リンク化
ポイントは「リンク化する前にテキストをHTMLエスケープする」ことです。これで、URL以外の部分にタグや属性が紛れ込んでも無害化されます。そのうえで、HTMLの危険文字や日本語の句読点を含まない正規表現でURLを検出します。
安全な linkify 関数
// 1) HTMLエスケープ(XSS対策の要)
function escapeHtml(s) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function linkify(text) {
const escaped = escapeHtml(text);
// 空白・HTML危険文字・日本語句読点/括弧は URL に含めない
const urlRegex = /https?:\/\/[^\s<>"。、)」]+/g;
return escaped.replace(urlRegex, (url) => {
// 末尾の半角句読点・閉じ括弧はリンクの外に出す
const trail = (url.match(/[.,!?)\]]+$/) || [""])[0];
const link = url.slice(0, url.length - trail.length);
return `<a href="${link}" target="_blank" rel="noopener noreferrer">${link}</a>${trail}`;
});
}
使い方(textContent から読む)
const div = document.getElementById("content");
// innerHTML ではなく textContent から「生のテキスト」を取得して変換
div.innerHTML = linkify(div.textContent);
innerHTML ではなく textContent から読むのがコツです。生のテキストを取得してからエスケープ&リンク化するため、すでにある <a> タグを壊したり二重リンクにしたりしません。textContent と innerHTML の違いはHTML要素のテキストを変更する方法も参考になります。動作例
上記の linkify() に渡したときの出力です。
入力と出力
linkify('Google: https://www.google.com です');
// → Google: <a href="https://www.google.com" target="_blank" rel="noopener noreferrer">https://www.google.com</a> です
linkify('詳細は https://example.com。続き');
// → 詳細は <a href="..." ...>https://example.com</a>。続き(「。」はリンク外)
linkify('攻撃 https://x.com"onmouseover="alert(1)');
// → " が " にエスケープされ、属性は注入されない(XSS無効化)
本番では専用ライブラリも検討する
正規表現による自動リンク化は、国際化ドメイン・末尾記号・メールアドレス・www. 始まりなど考慮すべきケースが多く、完璧に作り込むのは大変です。ユーザー生成コンテンツを大量に扱うなら、実績のあるライブラリ(linkify-string / linkifyjs など)を使うほうが安全で確実です。
自作する場合の鉄則:
href に javascript: スキームが入らないよう、https?:// で始まるものだけをリンク化してください。今回の正規表現は https?:// 限定なのでこの点は満たしています。よくある質問(FAQ)
Qテキスト内のURLを自動でリンクに変換するには?
A①テキストをHTMLエスケープ → ②正規表現でURLを検出 → ③aタグに置換、の順で実装します。エスケープを先に行うことでXSSを防げます。置換時に
target="_blank" と rel="noopener noreferrer" を付けると安全です。Qなぜ先にエスケープが必要なのですか?
Aエスケープしないと、テキストに含まれる
<script> や属性がそのままHTMLとして解釈され、XSSの原因になります。先に < や " を実体参照へ変換しておけば、URL以外の部分が悪用されることを防げます。QURLの後ろの句読点までリンクになってしまいます。
A
[^\s]+ のような正規表現は空白以外をすべて含むのが原因です。文字クラスから句読点や括弧を除外し(例:[^\s<>"。、)」]+)、さらにマッチ後に末尾の . , ) ! などをリンクの外へ出すと自然になります。Q本番のサービスでも自作の正規表現で大丈夫ですか?
A小規模なら問題ありませんが、国際化ドメインやメールアドレス、
www. 始まりなど考慮点が多いため、大量のユーザー入力を扱うなら linkifyjs などのライブラリが安全で確実です。まとめ
テキスト内のURLを安全にリンク化する手順を整理します。
- 先にHTMLエスケープしてXSSを防ぐ(最重要)
- 正規表現は HTML危険文字・句読点を含めない(
[^\s<>"。、)」]+) - 末尾の
.)などはリンクの外へ出す target="_blank"とrel="noopener noreferrer"を付けるinnerHTMLではなくtextContentから読む(既存タグを壊さない)- 大規模なら ライブラリを検討、
https?://限定でjavascript:を排除
関連として、HTML要素のテキストを変更する方法(textContent・innerHTML・XSS対策)・特定のクラス内の要素をアンカーリンクで囲む方法もあわせて読むと、安全なDOM操作に強くなれます。

