◆ 普通に URL に戻り先をもたせる
  ◆ 戻り URL が URL に含まれる見た目がイヤなら location.replace で変えられる
  ◆ サーバ側へのクエリに含めたくないから # 使うとアンカーの制御がちょっと面倒
◆ unload するときに sessionStorage に一瞬保存して遷移先で取り出す flashStorage
  ◆ 特別遅いページでもないなら同 session のタブが複数あっても問題無いと思う

最初に書いておくと 基本的に私はページ内に戻るボタンなんていらないと思ってます
ブラウザの機能に戻るがあるのですからわざわざ画面内に付ける必要なんて無いと思います
詳細画面から一覧画面に戻るのなら 「戻る」 というより 「一覧」 というリンクがあるべきでしょうし 仮につけるとしても history.back を実行するだけで十分です
決まったリンク先じゃなくて移動してきた元のページに戻るのというのを自分で作るなんてムダでしか無いです

なのですが Chrome は戻るボタンで余計なトラブルを招いたりもしますし (65 でも起きる) ブラウザの戻るボタン風なものを作ってみようかと思ってやってみました

戻るボタン

サンプルに作るものは a ~ c.html には z.html へのリンクがあるだけで z.html には a.html のリンクと戻るボタンを用意します
x.js が全ページで読み込む JavaScript です
ここで履歴管理の処理を行います
z.html の戻るボタンでは a ~ c.html の遷移元のページへのリンクを作ります

URL

z.html では遷移元に戻りたいので a.html から z.html に遷移してきたとして そのときに a.html から遷移してきたというデータを渡す必要があります
単純かつ確実な方法だと URL です
クエリパラメータに遷移元の URL をつけて遷移すれば戻り先が URL から特定できます
仕組みも実装も簡単ですし 問題点も特に無いです

あえて上げるなら URL が気持ち悪いところです
URL 中に URL が含まれるのって見た目が悪いですからね
開いた直後に JavaScript で戻りリンク設定してそのあとすぐに location.replace で消してしまうことも可能ですが それだとリロードしたら情報が消える欠点があります

あとは サーバサイドにそういった情報を持ち込みたくない場合もあります
戻る進むなんかはクライアント側で完結するべきもので URL の一部とは言え サーバ側に余計なパラメータを送りたくないこともあります
たとえば戻り先を保存するキーに 「back」 を使っているとサーバサイドへのクエリに 「back」 というキーが使えなくなります
ブラウザの戻るの都合で サーバサイドとのやりとりに余計な制限というか考える事が増えるのは嫌ですからね
そんな都合でクエリパラメータに含みたくないだけなら代わりに # を使う手もあります
これだとサーバサイドのクエリパラメータに含まれませんが アンカー機能に影響します

flashStorage

別に URL にこだわらなくてもいいと思って sessionStorage で渡す方法を考えてみました
ページの unload のタイミングでデータをセットして移動先のページで取り出します
特別読み込みの遅いページでもなければタブを並べてそれぞれのリンクを続けて開いても大丈夫だと思います

この方法で作ってみました

flashStorage 版サンプル

[a.html]
<!doctype html>

<script src="x.js"></script>

<h1>a</h1>
<a href="z.html">Link</a>

[b.html]
<!doctype html>

<script src="x.js"></script>

<h1>b</h1>
<a href="z.html">Link</a>

[c.html]
<!doctype html>

<script src="x.js"></script>

<h1>c</h1>
<a href="z.html">Link</a>

[z.html]
<!doctype html>

<script src="x.js"></script>

<h1>z</h1>
<a class="back">Back</a>
<br>
<a href="a.html">Link</a>

[x.js]
const storage = (function() {
const flash_receive = tryJSONParse(sessionStorage.flash, {})
const flash_send = {}
sessionStorage.removeItem("flash")
window.addEventListener("beforeunload", eve => {
window.addEventListener("unload", eve => {
sessionStorage.flash = JSON.stringify(flash_send)
})
})

return {
flash: new Proxy(
{},
{
get(target, name, receiver) {
return flash_receive[name]
},
set(target, name, value, receiver) {
flash_send[name] = value
},
}
),
}
})()

function tryJSONParse(str, default_value) {
try {
return JSON.parse(str)
} catch (err) {
return default_value
}
}

window.addEventListener("unload", eve => {
storage.flash.back = location.href
})

window.addEventListener("load", eve => {
for (const backlink of document.querySelectorAll("a.back")) {
backlink.href = storage.flash.back || "javascript:history.back()"
}
})

storage.flash にプロパティを追加すると それが遷移先に送られます
storage.flash のプロパティを参照すると遷移元のページでセットされた値を参照できます

この機能を使って 遷移するときには今のページの URL を追加して ページ読み込み時に back クラス付きの a タグの href を設定します
戻り先がないときは history.back を使ってブラウザの戻る機能を使うようにしています
JavaScript なので戻れないならボタンを非表示にするなど好きに調整できます

beforeunload の中で unload のリスナを作っていますが これはこの unload のリスナを最後に実行させるためです
登録順なので unload を直接設定してしまうとページ内で設定した unload より先に実行されてしまいます
そうなると後から storage.flash に追加したものが反映されなくなるので最後になるようにちょっと変な方法にしてます


基本的にはこれで戻る機能が実現できるのですが リロードやブラウザのバックでも戻り先が更新されてしまいます

a.html → z.html -(リロード)→ z.html

このときに z.html の戻り先は a.html ではなく z.html になります

b.html → z.html → a.html -(ページバック)→ z.html

このときだと z.html の戻り先は b.html ではなく a.html です

history.state

これの対処は 最初に z.html に来たときの戻り先を覚えていればよいということになります
なので最初に開いたときに history.replaceState を使って history.state にデータを保存します
すでにデータがあるページなら flashStorage のデータでなく history.state を使用すれば最初に開いたときの戻り先が維持できます

リスナ部分はこうなります
window.addEventListener("unload", eve => {
storage.flash.back = location.href
})

window.addEventListener("load", eve => {
const state = history.state || {}
if (state.back) {
setBackLink(state.back)
} else {
const back = storage.flash.back
history.replaceState({ ...state, back }, null, location.href)
setBackLink(back)
}

function setBackLink(url) {
for (const backlink of document.querySelectorAll("a.back")) {
backlink.href = url || "javascript:history.back()"
}
}
})

これでリロードやページバックが入っても大丈夫になりました

この方法を使えば URL に戻り先をもたせておいてロード時に URL を書き換えて見た目をキレイにする方法でもリロードに対応できます

戻るの戻る

現状のものでは 戻るボタンのリンクを押して戻った先に戻るがあった場合にも遷移元に戻ります
a.html にも戻るボタンをつけたとして

c.html → z.html → a.html -(戻るリンク)→ z.html -(戻るリンク)→ a.html

こうなります
2 回目の z.html には戻るボタンとは言えリンクを使って a.html から通常の遷移をしてるわけなので戻り先は c.html ではなく a.html です
ブラウザのページバックを使って z.html に戻った場合は 2 回目の z.html で history.state が使われるので c.html に戻ります

あくまで ページ内のリンクによって遷移したわけですので この動きでいいと思います
ブラウザのページバックと同じになりますし 今回の目的はブラウザの戻るを使わず 同じ動きをさせることでしたから


でも実際には リスト→サブリスト→詳細 とリンクしてきて詳細からサブリスト戻って そこからさらに戻るのは詳細じゃなくリストであってほしいということはありそうです
その場合は storage.flash に保存するものを直近だけじゃなく 配列で履歴形式にすれば可能です
遷移するときには 遷移元から受け取った back の配列に今の URL を push して遷移先に渡します
戻るボタンによる遷移のときには push する代わりに 1 つ pop して配列を遷移先に渡します
戻るリンクを作るときは配列の一番最後を使います

c.html []

z.html [c.html]

a.html [c.html, z.html]
↓ 戻るリンク
z.html [c.html]

という感じです
履歴を保存していくとなると URL にデータもたせるのでは辛いので flashStorage の方法のほうが良さそうです