If Route-Validation returns false, it will show /error.vue but not render the component of /:pathMatch(.*)*.
~/plugins/route.ts:
export default defineNuxtPlugin(() => {
useRouter().addRoute({
path: '/:pathMatch(.*)*',
component: () => import('~/pages/404.vue'),
});
return {};
});
~/pages/404.vue:
<template>err</template>
~/pages/problem/[id]/index.vue:
<script lang="ts" setup>
definePageMeta({
validate(route) {
return new RegExp('^\\d+$').test(String(route.params.id));
},
});
</script>
<template>problem</template>
Navigation Tests:
✅ visit /foo -> render 404 page
✅ visit /foo/bar -> render 404 page
❌ visit /problem/foo -> render Nuxt Error Page
✅ visit /problem/foo/bar -> render 404 page
Nuxt routing is based on directories. "Catch-all" doesn't apply recursively.
What you seek is:
/pages
├─ /foo
│ └─ [nested].vue
└─ [root].vue
Related
When navigating into the same route you are currently at, the route query page added by the before enter route guard is lost.
This is due to the fact that router link to object does not contain query page, and it does not go into before enter hook anymore, since it is already in the same route.
I figured out that you can add it in your router push or link button and you need to add this whenever you know the view contain query page.
Example:
this.$router.push({ name: 'routeName', query: { page: 1 } });
Question:
Is there an elegant way to handle this in route guard?
Which hook should I use so that route query page can be kept even user navigate into the same route?
Example code:
Route
// Sample route
const routes = [
{
path: 'test',
name: 'Test',
component: TestPage,
beforeEnter: testPageGuard,
},
];
Route Guard
// Test Page Guard
testPageGuard: (to, from, next) => {
const { page = null } = to.query;
let finalNext;
if (!page) {
finalNext = {
...to,
query: {
...to.query,
page: 1,
},
};
}
if (finalNext) {
next(finalNext);
} else {
next();
}
}
View
// TestPage.vue
<template>
<!-- The problem can be reproduce when clicking this link
when you are already in route '/test' -->
<router-link :to="{ name: 'Test'}">
Test
</router-link>
</template>
<script>
export default {
name: 'Test',
};
</script>
Solution Figured:
Add query page to router link
// TestPage.vue
<template>
<!-- query page is added here -->
<router-link :to="{ name: 'Test', query: { page: 1 } }">
Test
</router-link>
</template>
<script>...</script>
I figured out that there are two more ways to do this.
Solution 1: Update at "beforeRouteUpdate" Hook
beforeRouteUpdate triggers when query param changes even when in the same route.
Hence we can remove the beforeEnter guard and the extra page query in the route link, and do query param page adding at that particular page.
Example Code
View
// TestPage.vue
<template>
<!-- The problem can be reproduce when clicking this link
when you are already in route '/test' -->
<router-link :to="{ name: 'Test'}">
Test
</router-link>
</template>
<script>
export default {
name: 'Test',
// Solution here
beforeRouteUpdate(to, from, next) {
if (!Object.prototype.hasOwnProperty.call(to.query, 'page')) {
next({
...to,
query: {
// This line is used to retain other query if there is any
...to.query,
page: 1,
},
});
} else {
next();
}
},
};
</script>
Solution 2: Update at "beforeEach" Hook
When query param changes even when in the same route, it actually go through beforeEach hook also.
Hence we can remove the beforeEnter guard and the extra page query in the route link.
Add meta tag hasQueryParamPage for that route and do query param page adding in the global beforeEach hook.
This design has better reusability if you have other pages that require the query param page.
Example Code
Route
// Sample route
const routes = [
{
path: 'test',
name: 'Test',
component: TestPage,
// add meta tag
meta: { hasQueryParamPage: true },
},
];
Router
// router.js
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
...
});
// Solution here
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => (record.meta.hasQueryParamPage))) {
let updatedNext = null;
if (!Object.prototype.hasOwnProperty.call(to.query, 'page')) {
updatedNext = {
...to,
query: {
// This line is used to retain other query if there is any
...to.query,
page: 1,
},
};
}
if (updatedNext) {
next(updatedNext);
return;
}
}
next();
});
I want to navigate to a specific section of a page from another page. So I added the scrollBehavior function in router object in nuxt.config.js file like this:
router: {
scrollBehavior(to) {
if (to.hash) {
return {
selector: to.hash,
behavior: "smooth"
};
}
}
}
My 'pages' directory tree is like this:
pages/
index.vue
parent.vue
random.vue
In default.vue of 'layouts' directory I wrote the navbar:
<button #click="$router.push({ name: 'parent' })">Parent One</button>
<button #click="$router.push({ name: 'parent', hash: '#sec2' })">Parent Two</button>
Inside parent.vue I have two sections:
<div class="sec-1" id="sec1">
<h1>Parent One</h1>
<p>...
</p>
</div>
<div class="sec-2" id="sec2">
<h1>Parent Two</h1>
<p>...
</p>
</div>
Now, The problem is When I click the 'parent two' button from random.vue file it doesn't work. but when I am in parent.vue file and click the button it scrolls to the second section. But I want to navigate to the second section from random.vue page. If I write the exact code in a vue project then it works fine but doesn't work in nuxt project. But I need to do it in my Nuxt project.
Looking at my answer here, you can setup the following in a ~/app/router.scrollBehavior.js file
export default function(to, from, savedPosition) {
console.log("this is the hash", to.hash)
return new Promise((resolve, reject) => {
if (to.hash) {
setTimeout(() => {
resolve({
selector: to.hash,
behavior: "smooth"
})
}, 10)
}
})
}
And it should work well. Maybe just silently fail when it does not find the selector or apply it only on specific paths.
I wanted to have route like privacy-policy or terms-condition in nuxt without having the page name likePrivacy-Policy or Terms-Condition . Is there a simple way to do that without customizing the route or anything.
If you do have a page that is called BoringPage.vue but want something more fancy, you could write this
<router>
{
alias: [
'/cool-page',
]
}
</router>
<template>
...
</template>
and have access to the BoringPage via /cool-page.
All you need to do, is to install the router-extras module.
Create file _slug.vue in pages directory
<template>
<h1>{{ this.slug }}</h1>
</template>
<script>
export default {
async asyncData({ params }) {
const slug = params.slug // When calling /privacy-policy the slug will be "privacy-policy"
return { slug }
}
}
</script>
Nuxt dynamic pages: https://nuxtjs.org/docs/2.x/directory-structure/pages#dynamic-pages
I am building a web app with two layouts (for login page, and dashboard). Each of them are represented as SPA application, so each has router-view. The main problem is 'How to connect them and redirect from one to another?'.
I have a App.vue - check if user is authorized. if yes - redirect to Dashboard.vue, else - redirect to Login.vue. Each of them has there own router-view.
An SPA should be a single html file which serves up your app and all the routes, so the basic structure should be:
HTML
<div id="app">
</div>
<!-- bundled file -->
<script src="app.js"></script>
app.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import App from './components/App.vue' // import Base component
// Import views to register with vue-router
import Login from './components/views/Login.vue'
import Dashboard from './components/views/Dashboard.vue'
const guard = function(to, from, next) {
// Check if user is logged in (you will need to write that logic)
if (userIsLoggedIn) {
next();
} else {
router.push('/login');
}
};
const routes = [{
path: '/login',
component: Login
},{
path: '/dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
guard(to, from, next); // Guard this route
}
}]
const router = new VueRouter({
mode: 'history', // history mode
routes
})
new Vue({
el: '#app',
router,
render: h => h(App) // mount base component
})
App.vue
<template>
<div>
<!-- Your layout -->
<!-- All views get served up here -->
<router-view></router-view>
</div>
</template>
I haven't tested that, but in this scenario every view component gets served up by App.vue which is mounted on the main vue instance. You then use the beforeEach guard to check that the user is logged in, if they are then you call next() which takes them to the route, if they are not then you redirect them to login.
vue-router has the ability to create custom guards for any route. You do not need 2 separate applications, just some safety with the routes in your router.
https://router.vuejs.org/en/advanced/navigation-guards.html
Your guard could be a function that checks for authentication.
Here's a full implementation tutorial from Auth0: https://auth0.com/blog/vuejs2-authentication-tutorial/
I'm trying to use vue-router to show different components for different route. However it doesn't seem to be working.
I have a codepen of the compiled program here.
My main.js is just defining the router and starting the vue application. It's importing the components and setting the routes.
import scss from './stylesheets/app.styl';
import Vue from 'vue';
import VueRouter from 'vue-router';
import Resource from 'vue-resource';
import App from './components/app.vue';
import Home from './components/home.vue';
import Key from './components/key.vue';
import Show from './components/show.vue';
// Install plugins
Vue.use(VueRouter);
Vue.use(Resource);
// Set up a new router
var router = new VueRouter({
mode: 'history',
routes:[
{ path: '/home', name: 'Home', component: Home },
{ path: '/key', name: 'Key', component: Key },
{ path: '/show', name: 'Show', component: Show },
// catch all redirect
{ path: '*', redirect: '/home' }
]
});
// For every new route scroll to the top of the page
router.beforeEach(function () {
window.scrollTo(0, 0);
});
var app = new Vue({
router,
render: (h) => h(App)
}).$mount('#app');
My app.vue is really very simple, just a wrapper div and the router-view
<script>
export default {
name: "app"
}
</script>
<template lang="pug">
.app-container
router-view
</template>
The three other components that router should be showing are equally as simple, each looks basically the same. Just the name and the h1 content are different.
<script>
export default {
name: "home"
}
</script>
<template lang="pug">
h1 Home
</template>
Webpack build everything into an app.js without any errors. I have a super simple index.html file that I open in Chrome.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sagely Sign</title>
</head>
<body>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
</html>
Looking at the console I see no errors. What I do notice is the URL stays the same, it looks like the router is not redirecting to /home.
file:///Users/username/Development/test-app/build/index.html
I would have expected it to change to the new route.
file:///Users/username/Development/test-app/build/index.html#/!/home
But even if I goto that route directly the home.vue component is not displayed.
The function you are using in the beforeEach method is a navigation guard. Navigation guards receive 3 parameters: to, from and next. From the Navigation Guards documentation:
Make sure to always call the next function, otherwise the hook will never be resolved.
Here, you just scroll at the top of the page but the hook is never resolved, therefore the router stops here, right after the scroll.
Write your function like this:
router.beforeEach(function (to, from, next) {
window.scrollTo(0, 0);
next();
});
And it should work.