Using Vue3. I want to set roles allowed in the route definition, then use that value in beforeRouteEnter. Something like:
{
path: "secure/page",
name: "SecurePage",
component: SecurePage,
params: {role: admin},
}
Then
const guard = {
template: `...`,
beforeRouteEnter (to, from, next) {
next(vm => {
if( 'admin' === vm.$route.params.role) {
}
})
},
}
As it doesn't work. Is it possible in any way ?
You cannot access to the router params definition from the view as you try. You need something like this:
{
path: "secure/page",
name: "SecurePage",
component: SecurePage,
meta: { role: 'admin' }
}
Then in the view instead of vm.$route.params.role use vm.$route.meta.role:
beforeRouteEnter(to, from, next) {
next(vm => {
if ('admin' === vm.$route.meta.role) {
}
})
}
Just so you know, the beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
More about meta fields you can find here:
https://router.vuejs.org/guide/advanced/meta.html
Related
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.
I have navigation guards to prevent visitors from viewing protected pages without being logged in. One of the pages I want them to see without login is a dynamic route e.g. example.com/dynamic_part. Below is my vuejs code:
router.beforeEach((to, from, next) => {
let token = window.sessionStorage.getItem("local_token");
let whitelist = [
"/",
"/register",
"/login",
"/dynamic_part/",
];
below works but it doesn't allow for the dynamic route "/dynamic_part/"
if (whitelist.includes(to.path)) {
below works for the dynamic route but breaks other route guards i.e. can't move to Products after logging in. I get this error: Error: Redirected when going from "/login" to "/Products" via a navigation guard.
whitelist.some(item => console.log(to.path.includes(item), item))
if (whitelist.some(item => to.path.includes(item))) {
The rest of the navigation guard:
if (token) {
next({
name: "Products",
});
} else {
next();
}
} else {
if (token) {
next();
} else {
next({
name: "Login",
});
}
}
});
What am I doing wrong and how can get all urls to work?
The problem here is all routes will match to.path.includes("/").
You need to separate the routes you want to match fully, with the ones you match with contains (you might want startsWith()?).
const whitelist = [
"/",
"/register",
"/login",
];
const dynamicWhitelist = [
"/dynamic_part/",
];
if (whitelist.includes(to.path) || dynamicWhitelist.some(item => to.path.includes(item))) {
/// etc
}
The more 'Vue-router-like' way of doing this is defining a meta object in your routes and testing against those.
//routes:
const routes = [
{
path: '/login',
component: Login,
meta: { allowAnon: true }
}
...
router.beforeEach((to, from, next) => {
let token = window.sessionStorage.getItem("local_token");
if(to.meta.allowAnon) {
//etc
See the docs here for more details.
I've built the below code in Vue router and it works perfect in Vue-CLI.
import store from "./../../store/index.js";
function getView(view) {
return () => import(`#/views/settings/${view}.vue`);
}
const routes = [
{
path: "/myProfile",
name: "MyProfile",
component: getView("MyProfile"),
beforeEnter: (to, from, next) => {
document.title = "Profile - " + store.getters.getAppName;
if (store.getters["userStore/authenticated"]) {
next();
} else {
next({ name: "Home" });
}
},
}
]
export default routes;
Now I am replacing Vue-CLI with Vite and it gives the below error.
TypeError: Failed to resolve module specifier '#/views/settings/MyProfile.vue'
When I remove the getView("MyProfile") function and directly use import as below, it works.
const routes = [
{
path: "/myProfile",
name: "MyProfile",
component: () => import('#/views/settings/MyProfile.vue'),
beforeEnter: (to, from, next) => {
document.title = "Profile - " + store.getters.getAppName;
if (store.getters["userStore/authenticated"]) {
next();
} else {
next({ name: "Home" });
}
},
}
]
Can someone please, explain why?
Can someone please, explain why?
This is due to Rollup Limitations. All imports must start relative to the importing file and import should not start with a variable.
So to get the GetView() function working, you have to replace the alias (#/) with relative or absolute path ../views or /src/views :
function getView(view) {
return () => import(`../views/settings/${view}.vue`);
}
Why it is working when you remove the getView() and you write directly the import directive ?
If you set a literal string, the alias is resolved (it end up to a relative or absolute path, respecting rollup requirement).
After trying lots of options, I finally found this solution.
import store from "./../../store/index.js";
async function getView(view) {
const comps = import.meta.glob("../views/**/*.vue");
const match = comps[`../views/${view}.vue`];
//For TS: const match: () => Promise<any> = comps[`../views/${view}.vue`];
return (await match()).default;
}
const routes = [
{
path: "/myProfile",
name: "MyProfile",
component: () => getView("settings/MyProfile"),
beforeEnter: (to, from, next) => {
document.title = "Profile - " + store.getters.getAppName;
if (store.getters["userStore/authenticated"]) {
next();
} else {
next({ name: "Home" });
}
},
}
]
export default routes;
Hope, this will solve the problem. (This works for any route.)
A little bit late but this should be the answer to your question, dynamic import in different bundlers will definitely have different behavior
We have Vite's case covered in the official documentation here:
https://router.vuejs.org/guide/advanced/lazy-loading.html#with-vite
Hope that will help :)
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()
}
I'm using Vue Router and setting the meta object field, which is used to set the page title and description.
Right now I set up routes like this:
[...
{
path: '/page1',
component: Page1Component,
meta: {
title: 'Title for page1'
}
}
...]
and then synchronize this with the DOM:
router.beforeEach((to, from, next) => {
document.title = to.meta.title;
next();
});
One of my routes, I want to use a query string in the title, but I can't pass a function to the meta object. Is there a way this can be done, without defining the title in the component?
For example, what I'd want to do:
[...
{
path: '/page1',
component: Page1Component,
meta: (route) => {
title: `dynamic title is ${route.query.param}`
}
}
...]
VueRouter doesn't support setting a route's meta property to be a function like you're trying to do.
But you could have your title property be able to also be set as a function which takes in your route as a param:
{
path: '/page1',
component: Page1Component,
meta: {
title: route => `dynamic title is ${route.query.param}`
}
}
And then add a check in the beforeEach hook to set the document.title to the returned value of the title function, in the cases where it is a function:
router.beforeEach((to, from, next) => {
const { title } = to.meta;
document.title = typeof title === 'function' ? title(to) : title;
next();
});
As said in the vue router documentation, the to and from objects in navigation guards are both route object, like the $route variable accessible in the component.
Therefore, you can do this :
router.beforeEach((to, from, next) => {
document.title = to.query.param;
next();
});