router.back で他のサイトに行ってしまうのを止めたい
- 公開日:
自サイト上に配置した戻るボタンを押した時に、一つ前の履歴が別サイトだったら自サイトのフロントページに遷移させたい。
この記事では Vue Router の history モードのみ扱います。
試したこと
Vue Router のグローバルビフォーガードを使う
router.beforeEach()
を使うと、router のページ切替時に何か処理を行なうことができます。しかしこれは他サイトに遷移する場合には機能せず、今回の目的には適していません。
何か他の方法で、他サイトに遷移してしまう瞬間を判断する必要があります。
history.length を使う
window.history
では、ブラウザの戻る・進むの履歴(以下セッション履歴)の情報を得たり、操作したりすることができます。
history.length
には、現在のセッション履歴のアイテム数が記録されているので、アクセスされたときの length を保持しておけば、戻るによって自サイトを離れてしまう瞬間を捉えられるのではないかと考えました。
ところが、そういったコードを書いて動きを見てみると、これは無理であることが分かります。
戻る・進むでは history.length は変化しない
ユーザーが history.length が 2
の状態でページ A に来て、ページ B → C と遷移したあとに 2 回戻ったとします。ページ C に来た時点で history.length は 4
になっていますが、2 回戻ったあとも history.length は 4
のままです。
それもそのはずで、history.length は、飽く迄セッション履歴のアイテム数を指しています。1 つ戻ると、戻る側の履歴が 1 つ減って、進む側の履歴が 1 つ増えます。逆も然りで、何度戻ったり進んだりしようが、履歴の数自体は一定です。これが変化するのは、リンクを踏んだり、スクリプトによってページ遷移したりしたときだけです。
history.state.key を使う
セッション履歴のアイテムには、history.pushState()
や history.replaceState()
を使うことで、state と呼ばれるデータを保存することができます。
Vue Router は、この state に key というデータを保存しています。
そこで、アクセスされたときの key を保存しておけば、他サイトに遷移する瞬間を判断できるのではないかと考えました。ところが、これも割と上手くいくものの対応できないケースがあります。
更新すると key が変わってしまう
ページ A に来て、更新してページ上の戻るボタンを押したとします(そんなことされるかはさておき)。
最初のページ A に来た時点で key を sessionStorage に保存します。更新された時に sessionStorage から key を取り出します。
ところが、更新した時に、Vue Router は現在の履歴の state に key があるかどうかに拘らず、key を上書きしてしまいます。
そのため、保存しておいた key と一致せず、戻るボタンを押すと他サイトに遷移してしまいます。
最終的に実装した仕組み
自分で history.state
に canGoBack
というキーで真偽値のデータを入れるようにします。
SPA 起動時に現在の state を見て、state 自体または state.canGoBack
が設定されていなければ、false
に設定します。
ページ遷移時には、state.canGoBack
が設定されていなければ、true
に設定します。
こうすることで、history.state.canGoBack
を見れば、現在のルートから一つ戻っても良いのかが分かります。
Nuxt.js のプラグインにするとこんな感じです。
// plugins/replaceState.client.js
export default ({ app }) => {
const history = window.history
const initState = history.state
switch (initState && initState.canGoBack) {
case null:
case undefined:
// console.log('初回アクセス')
const stateCopy = initState === null ? {} : Object.assign({}, initState)
stateCopy.canGoBack = false
history.replaceState(stateCopy, '')
// break
// default:
// console.log('戻る・進むまたは更新によるアクセス')
}
app.router.afterEach(() => {
const state = history.state
if (state.canGoBack === undefined) {
// console.log('<nuxt-link>, $router.push() 等による遷移')
const stateCopy = Object.assign({}, state)
stateCopy.canGoBack = true
history.replaceState(stateCopy, '')
}
// else {
// console.log('$router.go(), 戻る・進む等による遷移')
// }
})
}
プラグインはアプリの起動よりも前に実行されるため、Vue Router による history.state.key
設定もまだ行なわれていません。
一度も history.pushState() / .replaceState()
によってデータを格納していない場合、history.state
は null
なので、短絡評価などによる null チェックを行なわねばなりません。
一方、Vue Router のグローバルな After フック(app.router.afterEach()
)では、Vue Router の key 設定が済んだ状態なので、history.state
が null
になることはありません。
そのため、state.canGoBack
が undefined
であるかどうかだけ確認します。
nuxt.config.js でプラグインを登録します。.client.js
で終わるファイルは自動的にクライアントサイドのみで実行されます。
// nuxt.config.js
export default {
plugins: [
'~/plugins/replaceState.client',
],
}
戻るボタンが設置されているテンプレートでは、window.history.state.canGoBack
を確認して、遷移先を変更します。
$router.back()
メソッドもありますが、内部で .go(-1)
してるだけなので使う必要はありません。
<template>
<button type="button" @click="goBack">1つ前のページに戻る</button>
</template>
<script>
export default {
methods: {
goBack() {
window.history.state.canGoBack
? this.$router.go(-1)
: this.$router.push({ name: 'index' })
},
},
}