Adding default query to all router links in Vue? - vue.js

I'm trying to find a way to add default query to all router-links on a page (in all components that it has)?
For example, I want all links on a page to end with argument: utm_campaign=from_our_friends.
And this page uses components that are also used by other pages.

You can add a navigation guard to the page component that adds an extra query param to the next destination:
beforeRouteLeave(to, from, next) {
const query = { ...to.query, utm_campaign: "from_our_friends" };
next({ query });
}

Based on the answer by #Majed Badawi, I ended up applying this solution:
beforeRouteLeave(to, from, next) {
const query = {
...to.query,
utm_campaign: 'from_our_friends'
};
if (!to.query.utm_campaign) { // otherwise you'll get 'infinite redirection' error
next({ path: to.path, query });
} else {
next()
}
}

Related

Dynamically add a route in a Nuxt3 middleware

I have a Nuxt3 project where I'd like to add new routes based on an API call to a database. For example, let's say a user navigates to /my-product-1. A route middleware will look into the database and if it finds an entry, it will return that a product page should be rendered (instead of a category page, for example).
This is what I came up with:
export default defineNuxtPlugin(() => {
const router = useRouter()
addRouteMiddleware('routing', async (to) => {
if (to.path == '/my-awesome-product') {
router.addRoute({
component: () => import('/pages/product.vue'),
name: to.path,
path: to.path
})
console.log(router.hasRoute(to.path)) // returns TRUE
}
}, { global: true })
})
To keep it simple, I excluded the API call from this example. The solution above works, but not on initial load of the route. The route is indeed added to the Vue Router (even on the first visit), however, when I go directly to that route, it shows a 404 and only if I don't reload the page on the client does it show the correct page when navigated to it for the second time.
I guess it has something to do with the router not being updated... I found the following example in a GitHub issue, however, I can't get it to work in Nuxt3 as (as far as I'm aware) it doesn't provide the next() method.
When I tried adding router.replace(to.path) below the router.addRoute line, I ended up in an infinite redirect loop.
// from https://github.com/vuejs/vue-router/issues/3660
// You need to trigger a redirect to resolve again so it includes the newly added
route:
let hasAdded = false;
router.beforeEach((to, from, next) => {
if (!hasAdded && to.path === "/route3") {
router.addRoute(
{
path: "/route3",
name: "route3",
component: () => import("#/views/Route3.vue")
}
);
hasAdded = true;
next('/route3');
return;
}
next();
});
How could I fix this issue, please?
Edit:
Based on a suggestion, I tried using navigateTo() as a replacement for the next() method from Vue Router. This, however, also doesn't work on the first navigation to the route.
let dynamicPages: { path: string, type: string }[] = []
export default defineNuxtRouteMiddleware((to, _from) => {
const router = useRouter()
router.addRoute({
path: to.path,
name: to.path,
component: () => import ('/pages/[[dynamic]]/product.vue')
})
if (!dynamicPages.some(route => route.path === to.path)) {
dynamicPages.push({
path: to.path,
type: 'product'
})
return navigateTo(to.fullPath)
}
})
I also came up with this code (which works like I wanted), however, I don't know whether it is the best solution.
export default defineNuxtPlugin(() => {
const router = useRouter()
let routes = []
router.beforeEach(async (to, _from, next) => {
const pageType = await getPageType(to.path) // api call
if (isDynamicPage(pageType)) {
router.addRoute({
path: to.path,
name: to.path,
component: () => import(`/pages/[[dynamic]]/product.vue`),
})
if (!routes.some(route => route.path === to.path)) {
routes.push({
path: to.path,
type: pageType,
})
next(to.fullPath)
return
}
}
next()
})
})
I suggest you use dynamic routing within /page directory structure - https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes
The [slug] concept is designed exactly for your usecase. You don't need to know all possible routes in advance. You just provide a placeholder and Nuxt will take care of resolving during runtime.
If you insist on resolving method called before each route change, the Nuxt's replacement for next() method you're looking for is navigateTo
https://nuxt.com/docs/api/utils/navigate-to
And I advise you to use route middleware and put your logic into /middleware/routeGuard.global.ts. It will be auto-executed upon every route resolving event. The file will contain:
export default defineNuxtRouteMiddleware((to, from) => {
// your route-resolving logic you wanna perform
if ( /* navigation should happen */ {
return navigateTo( /* your dynamic route */ )
}
// otherwise do nothing - code will flow and given to.path route will be resolved
})
EDIT: However, this would still need content inside /pages directory or some routes created via Vue Router. Because otherwise navigateTo will fail, as there would be no route to go.
Here is an example of one possible approach:
https://stackblitz.com/edit/github-8wz4sj
Based on pageType returned from API Nuxt route guard can dynamically re-route the original URL to a specific slug page.

How to route to the route's children path in Vue

I'm trying to route to a different route in vue after clicking the back button in the browser. Below is what I have done:
beforeRouteLeave (to, from, next) {
if (to.path === '/place-ad') {
console.log("path is /place-ad")
next('/place-ad?ad_id=' + 12345)
} else {
console.log("path is I don't know")
next()
}
The problem with the above is that I end up with the error:
VueJs Maximum call stack size exceeded
If I now change the if statement check to to.path !== '/place-ad' then there is no error but it doesn't route me to where I want, in this case the else clause gets triggered. I've seen many other similar stackoverflow questions but from what I've seen they all want to reroute to a completely different route. Whereas I'm trying to route to the route's children path.
ie: /place-ad?ad_id=' + 12345 instead of just /place-ad. How do I achieve this? Thank you.
So you want to add a query param on a specific route, if it is not set?
You can also do it like this in your place-ad page component:
<script>
export default {
created () {
if (!this.$route.query.ad_id) {
this.$router.push({
query: {
ad_id: '12345',
...this.$route.query
}
})
}
}
}
</script>
Or, if you have to stick to the beforeRouteLeave, this one should work too:
beforeRouteLeave (to, from, next) {
if (to.path === '/place-ad') {
if (!to.query.ad_id) {
return next({
path: to.path,
query: {
...to.query,
ad_id: '12345'
}
})
}
}
return next()
}

Prevent route change on refresh with Vue navigation guards

I have navigation guards working perfectly based on a userTp value, however whenever the user reloads a page it redirects them to name: 'login'.
I would like to prevent a route change on refresh. Is there a simple way to do this within navigation guards?
router.beforeEach(async (to, from, next) => {
if (to.meta.requireAuth) {
if (!store.state.auth.user) {
await store.dispatch(AUTH_REFRESH)
}
if (to.meta.userTp === store.state.auth.user.userTp) {
next()
window.scrollTo(0, 0)
} else {
next({ name: 'login' })
window.scrollTo(0, 0)
}
} else {
next()
window.scrollTo(0, 0)
}
})
EDIT:
I found a semi-workaround for this, but it's not perfect. It allows users to access pages if they know the correct page url path, but since my setup has a server-side userTp check before sending data it just shows the page template but without any data in it.
My temp solution:
(Added as the first if statement)
router.beforeEach(async (to, from, next) => {
if (to = from) {
if (!store.state.auth.user) {
await store.dispatch(AUTH_REFRESH)
to()
}
}

vue-router next method not working always in beforeach

i'm creating a navigation guard for redirect user to some page,
i get auth status from vuex:
state: {
auth: false,
},
and in vue-router beforeach, i set a condition that when auth state is false and route is not adminAuth,
redirect to adminAuth route
var auth = store.state.auth
if (!auth){
if( to.name !== "adminAuth" ){
next( { name: 'adminAuth' } )
}
}
problem is when route changed first time, next() not working properly, but second time, working properly!
can you help me?
You want to intercept the navigation request using beforeResolve or beforeEnter because beforeEach is too soon and some attributes of the logic you want to use have not been processed yet, so it is possible on second navigation that some underlying logic appears resolved because the next request is accessing something set by the previous request.
const router = new VueRouter({
routes: { ... }
})
router.beforeResolve((to, from, next) => {
if(!router.app.$store.state.auth && to.name !== 'adminAuth') {
next( { name: 'adminAuth' } )
}
next()
})

Params field is empty in $router.push

Consider this:
this.$root.$router.push({
path: '/dashboard',
params: { errors: 'error' },
query: { test: 'test' }
})
I use this in my component to redirect to another URL, and some error has occured. The problem is that when I want to access params field in dashboard component, it's empty. The query field works well. I'm trying to access it by this.$route.params.errors.
You can use params only with named paths (i think).
Example:
//route (in your router file should have "name")
{ path: '/errors', name: 'EXAMPLE', component: ... }
//navigating
this.$router.push({
name: 'EXAMPLE',
params: { errors: '123' }
});
Now it will have correct value in this.$route.params.
If you don't want to use named routes you can try this:
ES6
this.$root.$router.push({
path: `/dashboard/${error}`,
query: { test }
})
ES5
this.$root.$router.push({
path: '/dashboard/' + error,
query: { test: 'test' }
})
I faced the similar issue where in one of my views (component). I was trying to navigate (programmatically) from /foo/bar to /foo/bar/123, but the route param was not available later in the component. My relevant navigation code looked like below:
methods: {
save_obj() {
let vm = this;
// Make AJAX call to save vm.my_obj, and on success do:
let v = `${vm.$route.path}/${vm.my_obj.id}`;
console.log("Loading view at path: "+v);
vm.$router.push({ path: v });
},
...
}
It would print the expected log (e.g., Loading view at path: /foo/bar/112), however, the loading of data in the created() hook would not receive the value of route param. My failing created() code looked like below:
created: function() {
console.log("Loading object details.");
let vm = this;
let cid = vm.$route.params.id; // <---- This was the problem
vm.$http.get('api/'+cid)
.then(function (res) {
if (res.data.status == "OK") {
vm.my_obj = res.data.body;
} else {
vm.setStatusMessage(res.data.body);
}
})
.catch(function (error) {
console.log(error);
vm.setStatusMessage("Error: "+error);
});
}
The solution was indicated in the third note here quoted below :
Note: If the destination is the same as the current route and only
params are changing (e.g. going from one profile to another /users/1
-> /users/2), you will have to use beforeRouteUpdate to react to changes (e.g. fetching the user information).
I had to do the following in my component:
Change the line let cid = vm.$route.params.id; in created() to let cid = vm.course.id
and, add the following to the component:
beforeRouteUpdate(to, from, next) {
if (to.params.id) {
this.my_obj.id = to.params.id;
}
// Some other code specific to my app
next();
}
I hope this helps someone stuck with the similar issue.
If you want to send a parameter with a query parameter you can use that syntax like that
this.$router.push({
path: this.localePath(`/bookings/${requestReservation?.attributes?.booking_id}`),
query: { requestReservation: requestReservation }
})
You can access it on the next page like that
this.$route.query.requestReservation
If you want send it fro nuxt-link than its syntax like that
<nuxt-link
:to="{ path: '/bookings/'+ requestReservation.attributes.booking_id,
query: { requestReservation } }">
Booking
</nuxt-link>
You can access it on the next page same like previous
this.$route.query.requestReservation