エラー概要
Vue Router で router.push や router.replace を連続で同じパスに実行すると、次のようなコンソールエラーが発生します。
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location "/xxx"
at createRouterError (vue-router.esm.js:...)
これは「現在の場所と同一のルートへ再遷移しようとしたため、Vue Router が重複を回避した」という警告です。Promise が reject されるため、Unhandled Promise Rejection として表示されます。
再現ケース
下記のような二重クリックイベントが典型的です。
<template>
<article @click="jump(id)">
<button @click="jump(id)">詳細を見る</button>
</article>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function jump(articleId) {
router.push({ name: 'ArticleDetail', params: { id: articleId } })
}
</script>
button とその親要素の両方に同じ遷移処理が設定されているため、1 回のクリックで 2 回 push が呼ばれてしまいます。
解決方法
1. イベント伝播を止める
最も根本的な対処です。子要素のイベントハンドラで stopPropagation を呼び出します。
<button @click.stop="jump(id)">詳細を見る</button>
2. 同一パスへの遷移を事前にガード
現在のルートと遷移先が同一かを判定してスキップします。
function jump(articleId) {
const target = { name: 'ArticleDetail', params: { id: articleId } }
if (router.resolve(target).href === router.currentRoute.value.path) return
router.push(target)
}
3. グローバルでエラーを握りつぶす(応急処置)
>router.push/router.replace をラップして、NavigationDuplicated を無視する方法です。
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [...]
})
// push のラッパー
const originalPush = router.push.bind(router)
router.push = (to) =>
originalPush(to).catch((err) => {
if (err.name !== 'NavigationDuplicated') throw err
})
// replace も同様に
const originalReplace = router.replace.bind(router)
router.replace = (to) =>
originalReplace(to).catch((err) => {
if (err.name !== 'NavigationDuplicated') throw err
})
この方法は副作用を最小限に抑えつつ、エラーログを消すことができます。
まとめ
重複ルート遷移エラーは、イベントの二重発火や連打対策が不十分な場合に発生しやすいです。イベント伝播を制御するか、遷移前に同一パスかをチェックすることでクリーンなコードを保てます。