schedule2019-11-13

URLのフラグメント識別子(#以後の文字列)によってページを振り分ける方法

URLのフラグメント識別子(#以後の文字列)でページを振り分ける必要がありました。 この場合サーバでなくブラウザで振り分ける必要があります。

経緯は、もともと同じページ内でスクロールさせるつもりだったが別ページに分ける仕様に変わった。 既にハッシュタグ#付きのURLはQRコードで印刷されていたため、システムを修正しました。(期限がなくて大変だった。。) この記事はその備忘録です。

フラグメント識別子(fragment identifier)

URLの#に続く文字列はフラグメント識別子です。RFC3986

よく、目次やページ内のリンクで使われています。 タグのidと同じ文字列を指定すると、ページ内の該当箇所にスクロールしてくれます。 これはアンカーリンク(anchor link)ハッシュリンクとも呼ばれている。

フラグメント識別子はサーバに届かない

当初の考えではApacheで#/に変換してリダイレクトさせる予定でしたが、設定してもリダイレクトされない。 アクセスログのURLには#から先がなくなっていました。 明確な挙動について読み解けなかったんですが、#より後ろの文字列はサーバに届かないようです。

URI#fragent?query=1 このようにクエリパラメータを#以後に付けてしまうとサーバまで届かないのでご注意を。

フラグメント識別子によってページを振り分ける構成

フラグメント識別子はサーバに届かないことが分かりました。 そこで、#より前のURLのページを用意しブラウザからリダイレクトさせるようにします。

例として、次のURLで考えます。

https://host.domain.com/page#target1

フラグメント識別子はtarget1target2target3とあり、3つのページにリダイレクトさせます。 https://host.domain.com/pageはリダイレクトするだけのページです。

  1. https://host.domain.com/pageのページを用意する
  2. https://host.domain.com/page/target1https://host.domain.com/page/target2https://host.domain.com/page/target3のページを用意する
  3. https://host.domain.com/pageではフラグメント識別子毎に各ページへリダイレクトさせる

https://host.domain.com/page#target1でアクセスするとサーバと2往復することになってしまいますが、用意したページに辿りつけるため良しとします。

ブラウザでリダイレクトする

https://host.domain.com/page<head>に以下のスクリプトを書きます。

<script type="text/javascript">
console.log(location.href); // https://host.domain.comn/page#target1
console.log(location.hash); // #target1

if(location.hash.length == 0 || location.hash === '#'){
    // ②このページはリダイレクトするのみ
    // block redirect loop
    location.href = '/';
}else{
    // ②各ページへリダイレクトする
    location.href = location.href.replace('#', '/');
}
</script>

②では、URLを調べ#/に変えてリダイレクトさせています。

フラグメント識別子がなくとも、#はブラウザに残ります。 そのため、②のみだとURLがhttps://host.domain.com/page#の場合にリダイレクトループしてしまいます。 これを回避するためトップページへリダイレクトしていますが、404や#を除くだけの処理に変えても良さげ。

location.hashはURLのうち、#とそれに続くフラグメント識別子を収めた文字列です。

おわりに

ギリギリ期限に間に合いました。 URLの修正は大変ですので、早めに決めて連絡下さい。お願いします。

参考