3.1.1
https://jsfiddle.net/Lom7hxn5/
$router.replace or $router.push twice with same pathError object provides stacktrace (stack property)
No stack trace in the error object
When NavigationDuplicated error is thrown, there is no stack trace in the error itself so when error is reported through error reporting service like Sentry, there is absolutely no context to go with the error and so it's hard to figure out where that error originated.
Transpiled code for the NavigationDuplicated class looks like this:
https://github.com/vuejs/vue-router/blob/5141def3a21da479c2858c0d70db3cb438c3b7d0/dist/vue-router.common.js#L1935-L1946
which is problematic because instantiating such function won't create a stacktrace. It would work fine with non-transpiled code (although I'm not suggesting to not transpile it): https://github.com/vuejs/vue-router/blob/44c63a963f05ede229d8658a45b2388e244ce00b/src/history/errors.js#L1
One possible solution would be to manually set stack trace with:
this.stack = (new Error()).stack;
but I'm not sure how that would be done if transpilation is done by webpack/babel...
There is also one extra bug in that code. The line that instantiates error passes a route object to the error:
https://github.com/vuejs/vue-router/blob/fc42d9cf8e1d381b885c9af37003198dce6f1161/src/history/base.js#L125
but error doesn't do anything with it:
https://github.com/vuejs/vue-router/blob/44c63a963f05ede229d8658a45b2388e244ce00b/src/history/errors.js#L2
Saving route properties on the error object would also help in deciphering source of the error.
@rchl what navigator are you seeing this on? There are some limitations regarding error stacktraces that may lead to errors being uncomplete in browsers like IE9
I'm seeing this on every browser as it's not specific to a browser but to transpiled variant of the code. Instantiating such transpiled "class" is not gonna produce a stack trace as inheritance of Error is "faked" in that case (or at least not considered as proper subclass of Error by engines).
Weird, I do get a stack trace on Safari, Chrome and Firefox. IE is a bit of a different story

What are you expecting to see? Adding the stack manually or using Error.captureStackTrace yield the same stacktrace
I updated to vue-router from 3.0.7 to 3.1.2 and got this error. What does it mean? The current stack trace points to the internals of vue-router and vue.runtime and does not help. Thank you
edit: revert to 3.0.7 removed the error
@francoisromain see https://github.com/vuejs/vue-router/issues/2872#issuecomment-519073998 about the promise rejection
After many iterations I've created better testcase at https://jsfiddle.net/Lom7hxn5/ (also updated original comment).
Notice that in your screenshot you have two chevrons to expand. The one you've expanded is not actually the error object itself but something else (I think promise rejection exception). The error itself doesn't have a stack trace.
My new testcase just catches promise rejection and console.log's the error. That should also show stack trace but doesn't currently. Note: If using console.error instead of console.log, there will again be second chevron with stack trace but that's not from the error object itself.
Also here is a version using esm version of VueRouter (in browsers that support ES modules): https://jsfiddle.net/L8wykjns/5/
You can see that logged error actually has stack trace in this one.
I’m just wondering.. am I supposed to guard every route to prevent clicks if the route matches the current route? — Or is this just an issue with router?
upgraded to most recent and I _now_ I get this exception. This was introduced as part of the 3.1 new promise api. This seems to be a breaking change as I have to _change_ code to accommodate for the new version to keep the same behavior as before. Because the exception makes the router or page act erratic, this would seem to break semvers "add functionality in a backwards compatible manner".
Related:
https://github.com/vuejs/vue-router/issues/2880
https://github.com/vuejs/vue-router/issues/2878
https://github.com/vuejs/vue-router/issues/2872
Api change - https://github.com/vuejs/vue-router/issues/2873
Same issue. Don't want to guard every route to prevent an error that feels mostly cosmetic. For short term, might setup a global onError to swallow it, but that feels messy 😿
How to handle "NavigationDuplicated" error globally? I could not swallow this error using onError hook.
I use push (replace) function in lots of places and I would not want to guard every route too.
I understand it may be a bit confusing to see the UncaughRejection error in the console, so let me summarize what it's all about:
The error you see in the console is part of the new promise api: before, if no callbacks were supplied to router.push, errors were only sent to the global router error handler. Now, because both push and replace return a promise, if the navigation failure (anything that cancels a navigation like a next(false) or next('/other') also counts) is not caught, you will see an error in the console because that promise rejection is not caught. However, the failure was always there because trying to navigate to same location as the current one fails. It's now visible because of the promise being rejected but not caught.
This behavior doesn't happen with router-link: no errors are emitted (except globally). This is because it's catching the error by adding a _noop_ callback, so you don't need to catch anything when using router-link. You _can_ catch the rejection when doing programmatic navigation and ignore it:
router.push('/location', () => {})
But if you are using router.push in your code and you don't care about the navigation failing, you should catch it by using catch:
router.push('/location').catch(err => {})
The last version makes more sense as the Promise api is likely to become the default and the callback version to become deprecated.
This isn't a breaking change because the code still works the same it was before. The only exceptions are if you were doing one of these:
return router.push('/location') // this is now returning the promise
await router.push('/location') // this explicitly awaits for the navigation to be finished
However, router.push never returned anything before 3.1, so both cases were invalid usage. Even worse, if you were waiting for the resolution of router.push, it never worked because the function wasn't returning a promise, so the navigation wasn't even resolved nor failed after calling push. Now that it returns a Promise, it is possibly exposing bugs that were not visible before.
If you want to handle this globally, you can replace Router's push/replace functions to silence the rejection and make the promise resolve with the error instead:
import Router from 'vue-router'
const originalPush = Router.prototype.push
Router.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
I discourage from swallowing the errors like this because doing await router.push() will always resolve even when the navigation fails.
With the new navigation failures, you can replicate vue-router-next behavior:
import Router from 'vue-router'
const originalPush = Router.prototype.push
Router.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject)
return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch((err) => {
if (Router.isNavigationFailure(err)) {
// resolve err
return err
}
// rethrow error
return Promise.reject(err)
})
}
This was reported already at https://github.com/vuejs/vue-router/issues/2872 and https://github.com/vuejs/vue-router/issues/2873. This issue is about improving the stack trace of the Rejection so if you could keep the conversation about that, it would help me a lot! I hope that this answer, being more detailed brings clarity to the topic. I've also been looking at how this impacts Nuxt with their core team so the error doesn't appear if it shouldn't.
hey @posva thanks so much for the thorough breakdown. I think the confusion is coming into play regarding what you described when you said:
"However, the failure was always there because trying to navigate to same location as the current one fails. It's now visible because of the promise being rejected but not caught."
While I understand that the above is true, I don't think it was unreasonable for anyone to assume that the router's default behavior would be to handle the NavigationDuplicated usecase "out of the box"... I've been using vue-router for several years, and I thought this was just default functionality all along.
I'm absolutely a fan of the promise API and see some immediate benefits, but I've had to rollback vue-router to a pre 3.1 version because I don't have a way of cleaning up the errors that are being thrown without adding catch to all of my router.push's.
Perhaps we can spin up a separate "feature request" issue for handling NavigationDuplicated out of the box?
@posva It's not related to NavigationDuplicated but to stacktrace. on a this.$router.replace({ name: 'name' }) I have an error and I would love to know why but the false is not really helpful here

I'm glad you could find it useful @shayneo !
While I understand that the above is true, I don't think it was unreasonable for anyone to assume that the router's default behavior would be to handle the NavigationDuplicated usecase "out of the box"
I want to be clear about this: it wasn't handled out of the box before, the navigation failure was ignored. It was a behavior that could cause bugs (as explained in the comment) and we are now making it visible.
I've had to rollback vue-router to a pre 3.1 version because I don't have a way of cleaning up the errors that are being thrown without adding catch to all of my router.push's.
Yes, you do have a way to do it, I added a code snippet to override the function if the errors in the console are too noisy. You can also include a console.warn(err) inside to keep track of them and progressively migrate. Let me know if there are improvements to apply or cases it doesn't handle.
Having an option to silence that specific navigation failure doesn't seem like a good api choice imo, as it's a failure like others and I think people just got used to not having to handle it. It's achievable in user land with the snippet above though. It's something that could evolve in future major versions, but this behavior is consistent with the callback version of navigation functions. For it to evolve it will be necessary to redefine what a navigation success means (everything that isn't a success is a failure and makes the Promise rejection). Right now, navigation succeeds if the navigation resolves correctly to what we pass as the first argument (the target location). Any guard navigation aborting or redirecting, not navigating (same location) as well as errors, abort the navigation, making it reject. If you think this isn't the right way, it would help to discuss it in an RFC with pros/cons and other alternatives
@frlinw Errors are being improved, there were no proper errors before so some rejections still need to be improved
So, do we have to handle catch from now on or this is something which may get completely different?
Should I update router.push and other affected everywhere in my app?
So, do we have to handle catch from now on or this is something which may get completely different?
Should I updaterouter.pushand other affected everywhere in my app?
Well, I am considering to put catch on all $router.push in my app.
This error sounds semantically wrong. In theory, you should be able to redirect (a.k.a push) route to be anything at anytime. Say I am in /route and upon clicking on the let's say app logo (which redirects to/ again), this is not an error - it's a very common user behaviour.
I don't know what was the use case for such feature but I should not implement any router logic in my application, plus suppressing errors currently sounds more dangerous then downgrading the version - which I am doing atm.
@jd-0001 Only when needed, please see https://github.com/vuejs/vue-router/issues/2881#issuecomment-520554378
@burakson the error is the promise not being caught. The rejection is because the navigation didn't resolve to the location that was specified. If you want to discuss what would be an error and what cannot, please do it in an RFC as I said above as it's more complicated than it looks like and have quite some implications. Prior to an RFC an issue can be opened in the RFCs repo, but it still requires some work with pros and cons. But please, read the comments I wrote above. I would appreciate if the issue was not hijacked to talk about something else
@posva would you mind pointing me in the right direction for an RFC?
I'm happy to start one, and we can link here so that folks that are running into this can contribute.
It seems like there is a bit of a catch-22 in that "expected behavior" is conflicting with legitimate error handling, and I would love to see this discussion harnessed into something productive for the project. Thanks 😄
@shayneo it's at vuejs/rfcs repo. You will find all info there. Happy to hear other alternatives and ideas for a promise based api regarding navigation!
Finally managed to get a better look. The problem only happens on Sentry and adding the stack trace does indeed fix the problem, I will push a fix soon. The only exception is IE, which cannot seem to be fixed, no matter how the Error instance is extended nor using regular Errors seems to help.
The problem only happens on Sentry
Technically that's not exactly true. Exception is stack-less regardless if running with sentry or not but when having devtools open, engine logs some extra stuff so you can actually see a (separate) stacktrace. But if you have devtools closed and trigger the error then you won't have that.
But that's a detail. Thanks for fixing.
Compatible with version 3.1 below, no catch error is read
js
const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
try {
return originalPush.call(this, location).catch(err => err)
} catch (error) {
console.log(error)
}
}
If someone is interested in TypeScript version you could use the following snippet:
/*
* Preventing "NavigationDuplicated" errors in console in Vue-router >= 3.1.0
* https://github.com/vuejs/vue-router/issues/2881#issuecomment-520554378
* */
const routerMethods = ['push', 'replace'];
routerMethods.forEach((method: string) => {
const originalCall = (Router.prototype as any)[method];
(Router.prototype as any)[method] = function(location: any, onResolve: any, onReject: any): Promise<any> {
if (onResolve || onReject) {
return originalCall.call(this, location, onResolve, onReject);
}
return (originalCall.call(this, location) as any).catch((err: any) => err);
};
});
The code above also modifies .replace method
I only see Uncaught Exception: Object with no stacktrace, line number...etc
For now, I'm using a workaround like this:
this.$router.currentRoute.name !== 'Home' &&
this.$router.replace({ name: 'Home' })
It will avoid the NavigationDuplicated
Hello,
I'm a newbie on Vue Js and I have this error "uncaught error" when I make this on my Vue JS project :
beforeRouteEnter: (to, from, next) => {
// Je vérifie que le Local Storage n'est pas vide
if (localStorage.length > 1) {
//Je récupère les infos du Local storage qui seront sous la forme "string"
let recupLocalStore = localStorage.getItem(`userInfoConnect`);
//Je transforme cette string en JSON avec JSON.parse pour la manipuler
recupLocalStore = JSON.parse(recupLocalStore);
//Je récupère certaine valeur que je réutilise
let id_utilisateur = recupLocalStore.id_user;
//Je vérifie ensuite que l'id utilisateur trouvé dans le Local Storage correspond au type utilisateur voulu
if (id_utilisateur == 1) {
//L'id correspond j'affiche la page
next();
} else {
//L'id ne correspond pas je retourne à la page de login
next("/login");
}
} else {
next("/login");
}
},
And i try this but I've got "Cannot read property '$router' of undefined at beforeRouteEnter" 👍
beforeRouteEnter: (to, from, next) => {
// Je vérifie que le Local Storage n'est pas vide
if (localStorage.length > 1) {
//Je récupère les infos du Local storage qui seront sous la forme "string"
let recupLocalStore = localStorage.getItem(`userInfoConnect`);
//Je transforme cette string en JSON avec JSON.parse pour la manipuler
recupLocalStore = JSON.parse(recupLocalStore);
//Je récupère certaine valeur que je réutilise
let id_utilisateur = recupLocalStore.id_user;
//Je vérifie ensuite que l'id utilisateur trouvé dans le Local Storage correspond au type utilisateur voulu
if (id_utilisateur == 1) {
//L'id correspond j'affiche la page
next(this.$router.push("/profil"));
} else {
//L'id ne correspond pas je retourne à la page de login
next("/login");
}
} else {
// next("/login");
console.log("bidon");
this.$router.push("/login");
}
},
I don't understand what I have to do. Can You help me please?
Cordially,
This issue is still occurring when trying to programmatically navigate to the same route with a different query parameters. For example, if I have a method as follows:
navigateToUser(userId) {
this.$router.push({ name: "UserRoute", query: { id: userId } })
}
Clicking once works fine, as there is no id set in the query parameters yet. However upon 2nd click the route will throw an error of NavigationDuplicated.
Not only that but putting a catch(err => {}) to the end of the push method will stop the error from being shown but also prevent the url from updating.
The only solution we found is to first replace the url with empty query parameters and then push the correct query params, but that completely breaks the user experience of being able to go back to previous URLs
Like @LanFeusT23 I've also had issues where NavigationDuplicated error is thrown when trying to push with a new query param.
Is there a recommended path for this use case? It's often desired for things like pagination for example if I'm currently at myapp.com/things?offset=0 I might want to...
this.$router.push({ name: this.$route.name, query: { offset: 10 }})
vue-router 3.13 依旧有这个错误,虽然有方法解决
This RFC improves navigation failures and should address some of the problems people were facing regarding the uncaught promise rejections: https://github.com/vuejs/rfcs/pull/150
the navigation failure was ignored. It was a behavior that could cause bugs (as explained in the comment) and we are now making it visible.
Having an option to silence that specific navigation failure doesn't seem like a good api choice imo, as it's a failure like others and I think people just got used to not having to handle it.
@posva
Html:
<footer @click="navigate">Help</footer>
Js:
methods: {
navigate() {
this.$router.push("/help");
}
}
This footer is visible and clickable on hundreds of pages, and users often click footer links to a page they are already on (in this case the help page). A user can come and click a link in this footer a million times, even when they are already on the page the link routes to. _In fact my production error logs are 99% this NavigationDuplicated error._
There have been 5+ threads about this issue. The user/developer/customer is telling you that this a problem. You have a big usability issue on your hands here that is being ignored.
It seems to me - and apparently a lot of other vocal people - that this is a normal (successful even) navigation and not an error. Navigating to a route you are already should not log an "error". It might not be a perfect "success" but it shouldn't reject the promise as a failure. Rejection is for when the thing you expected to happen did not happen. In this case, the thing I expected to happen (stay on the same page) - successfully happened. It's a success, with a warning. I really don't care how the vue-router currently defines "success" and "failure" as a result of a promise - because the way you currently are doing it defies your userbase's (a bunch of angry entitled developers) perception of what a success/failure is - and that is why so many threads on this exist. You would think that the router should be smart enough to properly announce (warn) _NavigationDuplicate_ instead of clogging my production error logs with an error that is actually just normal, expected behavior.
Because I can give you a million cases where no one would want to catch it in production. Having a link on a page - to a page that you are already on - is not an error.
@click will do?.catch() after every push call this.$router.push("/help").catch(err => err); and have and lose the ability to catch real errors or have a million .catch() functions sprinkled throughout our codebases?This was a bad design decision. Usability comes first. You are going to fix this by redefining what a success and failure is. And I am about 20 seconds away from forking the whole vue-router over this and making a "not-sh*t-vue-router" lib.
You have a big usability issue on your hands here that is being ignored.
I did not ignore it. I listened to the feedback and came up with https://github.com/vuejs/rfcs/pull/150 which was well received. It addresses your concern too.
And to answer your question, no, I don't think a duplicated navigation it's an error.
Locking this thread as it's outdated after the RFC and comments like yours, which are negative and clearly a rant with sentences like _Because I can give you a million cases_ and _I have about a million of these buttons live in production_, it's not something I need to deal with.
Most helpful comment
I understand it may be a bit confusing to see the UncaughRejection error in the console, so let me summarize what it's all about:
The error you see in the console is part of the new promise api: before, if no callbacks were supplied to
router.push, errors were only sent to the global router error handler. Now, because bothpushandreplacereturn a promise, if the navigation failure (anything that cancels a navigation like anext(false)ornext('/other')also counts) is not caught, you will see an error in the console because that promise rejection is not caught. However, the failure was always there because trying to navigate to same location as the current one fails. It's now visible because of the promise being rejected but not caught.This behavior doesn't happen with
router-link: no errors are emitted (except globally). This is because it's catching the error by adding a _noop_ callback, so you don't need to catch anything when usingrouter-link. You _can_ catch the rejection when doing programmatic navigation and ignore it:But if you are using
router.pushin your code and you don't care about the navigation failing, you should catch it by usingcatch:The last version makes more sense as the Promise api is likely to become the default and the callback version to become deprecated.
This isn't a breaking change because the code still works the same it was before. The only exceptions are if you were doing one of these:
However,
router.pushnever returned anything before 3.1, so both cases were invalid usage. Even worse, if you were waiting for the resolution ofrouter.push, it never worked because the function wasn't returning a promise, so the navigation wasn't even resolved nor failed after callingpush. Now that it returns a Promise, it is possibly exposing bugs that were not visible before.If you want to handle this globally, you can replace Router's push/replace functions to silence the rejection and make the promise resolve with the error instead:
I discourage from swallowing the errors like this because doing
await router.push()will always resolve even when the navigation fails.With the new navigation failures, you can replicate vue-router-next behavior:
This was reported already at https://github.com/vuejs/vue-router/issues/2872 and https://github.com/vuejs/vue-router/issues/2873. This issue is about improving the stack trace of the Rejection so if you could keep the conversation about that, it would help me a lot! I hope that this answer, being more detailed brings clarity to the topic. I've also been looking at how this impacts Nuxt with their core team so the error doesn't appear if it shouldn't.