同じサイトでも「キャンペーンページだけ特別なバナーを出す」「?promo=summer が付いたときだけ案内を表示する」といったURLに応じた表示の出し分けはよくある要件です。ポイントは、URLのどの部分(パス・クエリ・ハッシュ)をキーにするかを最初に決めることです。部分の選び方を誤ると、誤発火したりブックマークで再現できなかったりします。この記事では3つの部分の使い分けと、それぞれの安全な実装方法を整理します。
- URLを
pathname/search/hashに分解して扱う方法 - パス・クエリ・ハッシュどれで出し分けるかの判断基準
- パス判定で
includesを使うと誤爆する理由と安全な書き方 - クエリパラメータでの出し分けとXSS対策
- 戻る/進む・ハッシュ変更に追従する
popstate/hashchange - JavaScript無効でも崩れないためのフォールバック設計
まずURLを「部分」に分解する
現在のURLは location から取得できますが、URL オブジェクトを使うと各部分を構造的に扱えて読みやすくなります。
// 例: https://example.com/campaign/?promo=summer#detail
const url = new URL(location.href);
url.pathname; // "/campaign/" … パス
url.search; // "?promo=summer" … クエリ文字列
url.hash; // "#detail" … ハッシュ
url.searchParams.get("promo"); // "summer" … 個別パラメータ
URL オブジェクトが便利new URL(location.href) なら searchParams でパラメータをそのまま取得できます。location.pathname などの個別プロパティでも同じ値が取れます。URL取得まわりの全プロパティは現在のURLを取得する方法(location・URLオブジェクト)で詳しく解説しています。パス・クエリ・ハッシュ、どれで出し分ける?
出し分けのキーにする部分は、用途で選びます。迷ったら次の早見表を基準にしてください。
| URLの部分 | プロパティ | 例 | 向いている用途 | サーバーに届く |
|---|---|---|---|---|
| パス | location.pathname |
/campaign/ |
ページ種別ごとの固定的な出し分け | 届く |
| クエリ | location.search |
?promo=summer |
動的なフィルタ・キャンペーン・状態 | 届く |
| ハッシュ | location.hash |
#tab-profile |
ページ内タブ・SPA表示・スクロール位置 | 届かない |
パスとクエリはサーバーへ送信されるため、サーバー側でも内容を出し分けたり集計したりできます。ハッシュ(
# 以降)はサーバーに送られません。リロードせずクライアントだけで表示を切り替えたいタブUIなどに向きます。以降の例では、対象要素を最初に hidden 属性で隠しておき、条件に合うときだけ表示します。まずHTML側で対象を隠しておきます。
<div id="campaign-banner" hidden>キャンペーン実施中!</div> <div id="summer-banner" hidden>夏のセール開催中</div>
表示用の共通ヘルパーを用意しておきます。
function show(id) {
const el = document.getElementById(id);
if (el) el.hidden = false; // hidden 属性を外して表示
}
パスで出し分ける(完全一致が基本)
特定のページでだけ要素を出すなら、パスを完全一致で判定します。includes() の部分一致は手軽ですが、意図しないURLにもマッチして誤爆しがちです。
const path = location.pathname;
// 完全一致(推奨):そのページだけに出す
if (path === "/campaign/") {
show("campaign-banner");
}
// 配下すべて(/campaign/ や /campaign/2024/ など)に出すなら startsWith
if (path.startsWith("/campaign/")) {
show("campaign-banner");
}
includes() は誤爆しやすいpath.includes("/campaign/") は /old-campaign/archive/ のような無関係なURLにもマッチしてしまいます。「そのページだけ」なら ===、「その配下すべて」なら startsWith() を使い分けてください。URLに文字列が含まれるかの判定パターンはURLに特定の文字列が含まれているか判定する完全ガイドにまとめています。クエリパラメータで出し分ける
?promo=summer のようなパラメータは URLSearchParams で取得します。値に応じた表示の出し分けは、選択肢が多いならオブジェクトマップが読みやすくなります。
const params = new URLSearchParams(location.search);
const promo = params.get("promo"); // 無ければ null
// 値 → 表示する要素ID の対応表
const banners = {
summer: "summer-banner",
winter: "winter-banner",
};
const id = banners[promo];
if (id) show(id);
textContentURLの値はユーザーが自由に書き換えられます。取得した値をそのまま
innerHTML に入れるとXSS(スクリプト注入)の危険があります。画面に表示する場合は textContent を使ってください。if / switch / マップでの分岐パターンや、リロードせずURLを更新するhistory.replaceState() まではURLパラメータを取得して処理を分岐する方法で詳しく解説しています。別ページへパラメータを引き継ぐならURLのパラメータを引き継ぐ方法も参考になります。ハッシュで出し分ける(hashchange で追従)
ハッシュ(#tab-profile など)は、リロードなしで表示を切り替えるタブUIに向きます。ハッシュは # を含んだ文字列で取得され、変わるたびに hashchange イベントが発火します。初回表示とイベントの両方で判定を走らせるのがポイントです。
function applyHash() {
const hash = location.hash || "#tab-home"; // 空なら既定タブ
document.querySelectorAll("[data-hash-section]").forEach((el) => {
// data-hash-section の値が現在のハッシュと一致する要素だけ表示
el.hidden = el.dataset.hashSection !== hash;
});
}
applyHash(); // 初回ロード時
window.addEventListener("hashchange", applyHash); // ハッシュが変わるたび
WAI-ARIA対応・キーボード操作・アニメーションまで備えたタブの実装はタブ切り替え機能の実装方法(URLハッシュ連動)で詳しく扱っています。URLの取得・操作全般は現在のURLを取得・操作する完全ガイド(パラメーター・ハッシュ・pushState)も参考にしてください。
共通の注意点:再判定とフォールバック
URLによる出し分けを実装するとき、見落としやすい2つの落とし穴があります。
1. 戻る/進む・履歴操作に追従する
ページ読み込み時に1回だけ判定するコードは、ブラウザの戻る/進むやリロードなしのURL更新に追従しません。ハッシュの変更は hashchange、ブラウザの戻る/進むは popstate で再判定します。
pushState() 自体は popstate を発火しないpopstate が発火するのは戻る/進む(や history.back() など)のときだけです。自分で history.pushState() / replaceState() を呼んでURLを書き換えても popstate は発火しないため、書き換えた直後に判定処理を手動で呼ぶ必要があります。function render() {
// ここで現在のURLを見て表示を更新する
}
render(); // 初回ロード
window.addEventListener("popstate", render); // 戻る/進む(pushStateした履歴へ戻ったときも)
window.addEventListener("hashchange", render); // ハッシュ変更
// 自分でURLを書き換えるときは popstate が発火しないので render() を呼ぶ
function navigate(url) {
history.pushState(null, "", url);
render();
}
2. JavaScript無効・SEOへの配慮
JavaScriptでの出し分けは、JSが無効・読み込み前・クローラーによっては反映されません。検索結果に出したい本質的なコンテンツはサーバーサイドレンダリングで出し、JSの出し分けはバナーや補助的な表示に留めるのが安全です。パラメータで内容が大きく変わるページは
canonical を適切に設定して重複評価を避けましょう。まとめ
URLに応じた表示の出し分けは、どの部分をキーにするかを決めることから始まります。
- パス:ページ種別の固定的な出し分け。
===/startsWith()で安全に判定(includes()は誤爆注意) - クエリ:動的なフィルタやキャンペーン。
URLSearchParamsで取得しtextContentで安全に表示 - ハッシュ:リロード不要のタブ・SPA表示。
hashchangeで追従 - 戻る/進むには
popstate/hashchangeで再判定する - 重要コンテンツはサーバー側で出し、JSは補助に留める
よくある質問(FAQ)
popstate、ハッシュの変更は hashchange イベントで判定を再実行してください。なお自分で pushState() / replaceState() を呼んだ場合は popstate が発火しないため、URLを書き換えた直後に判定処理を手動で呼ぶ必要があります。分岐とURL更新の詳細はURLパラメータを取得して処理を分岐する方法を参照してください。includes と === のどちらがよいですか?===、「その配下すべて」に出すなら startsWith() を使います。includes() は /old-campaign/ のような無関係なURLにもマッチするため誤爆しやすく、避けるのが無難です。?)とハッシュ(#)はどう使い分けますか?hashchange で検知できるのも特徴です。canonical URL を適切に設定し、検索に出したい重要なコンテンツは JavaScript 無効でも見えるようサーバーサイドレンダリングで出してください。JSによる出し分けは装飾・補助的な表示に留めるのが安全です。if / switch、選択肢が多ければオブジェクトマップが読みやすくなります。具体的な分岐パターンやリロードせずURLを書き換える方法はURLパラメータを取得して処理を分岐する方法にまとめています。

