In my nuxt project i have the following structure in my pages folder:
index.vue
tools
index.vue
_id.vue
page-not-found.vue
in my nuxt config i have this on router section:
router: {
extendRoutes(routes, resolve) {
routes.push(
{
name: '404',
path: '*',
component: resolve(__dirname, 'pages/page-not-found.vue')
},
)
},
}
when i land in url other than on my pages folder, i have the url and my 404 component showed (ex: my-domaine/other-path show 404 content). So that i want is with my route tools-id i want to fetch data depend on parameter id, if got data, i show the page with details, but if i got no data, i want to have the same behavior as my 404 route; like, in my url i have my-domaine/tools/parameter_value but my pages show the 404 content.
Take a look at the Nuxt documentation for error pages: https://nuxtjs.org/docs/concepts/views/#error-page.
You can create a layout file called error.vue which serves as a base for all error pages in your site. From that layout you can reference the appropriate error component. For example:
<template>
<div>
<NuxtLink to="/">Home page</NuxtLink>
<PageNotFound v-if="error.statusCode === 404" :message="error.message" />
</div>
</template>
<script>
export default {
props: ['error'],
layout: 'error'
}
</script>
Vue will automatically render the content of the error page on the invalid route. You can also programmatically open the error page by calling the context.error() method. For example:
asyncData ({ params, error }) {
return axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
}
If you're using the error function inside the asyncData or fetch methods, then you can access it like I showed in the answer above, otherwise in any other method defined in the Vue instance you can access it via this.error()
export default {
data: {...},
methods: {
validateRoute() {
if (404) {
this.error({statusCode: 404, message: "Page not found"})
}
},
axiosRequest() {
try {
const response = await axios.get('...');
return response;
} catch(err) {
this.error({statusCode: 404, message: err.message });
}
}
}
}
Related
I've been searching and tinkering for a while now without any luck. I'm looking to be able to catch errors and show an "Oops" page. Just to be clear, this isn't about 404 pages (which work fine).
I've reduced this for simplicity but in the below, before a page loads, it "attempts something which may fail". When that fails, I navigate to /error which shows an error page:
const router = createRouter({
routes: [
{
path: '/',
component: Index
},
{
path: '/something',
component: Something
},
{
path: '/error',
component: Error
},
{
path: '/:catchAll(.*)',
component: NotFound
}
]
})
router.beforeEach(async (to, from, next) => {
// attempt something which may fail
})
router.onError(() => router.push('/error'))
This all works fine, but it means that the /error path is navigable, and that a path change occurs. What I'd prefer is a way to be able to show an error component (Error) if an error occurs while keeping the url path the same.
Say I was on / and then I navigated to /something but "something failed", the url path would equal /something but the Error component would be used, rather than the Something component.
Any ideas? It seems like this should be manageable but so far I'm coming up blank.
Thanks
As Duannx commented above, a solution to this was to use a state to control this. Here's how I did it:
I created a Pinia store to hold an error variable:
import { defineStore } from 'pinia'
export const useGlobalStore = defineStore({
id: 'global',
state: () => ({
_error: false
}),
getters: {
error: (state) => state._error
},
actions: {
async setError(value) {
this._error = !!value
}
}
})
In the router, I catch any errors:
import { createRouter, createWebHistory } from 'vue-router'
import { useGlobalStore } from '../stores/global'
import routes from './routes'
const history = createWebHistory(import.meta.env.BASE_URL)
const router = createRouter({ history, routes })
router.beforeEach(async (to, from, next) => {
const globalStore = useGlobalStore()
try {
// Things which may fail here
next()
} catch (error) {
globalStore.setError(true)
return next()
}
})
export default router
In the root component (App.vue), I then check for the error state, and either show that or the router view based on whether or not the error state is set the true.
<template>
<Error v-if="globalStore.error" />
<RouterView v-else />
</template>
This works great, thanks Duannx.
I have App.vue file that I mounted function will redirect to maintenance page if maintenance set to true. When home page is landing it is not redirecting but when I do refresh the page it redirect to maintenance page.
mounted() {
const siteIsMaintenanceMode = this.$store.getters.getMaintenancMode;
if (siteIsMaintenanceMode) {
this.$router.push({ path: "/maintenance" });
}
this.preloadRoutes();
},
You can use beforeMounted method and bind data variable in data object as below;
data() {
return {
siteIsMaintenanceMode: this.$store.getters.getMaintenancMode
};
},
beforeMount(){
if (siteIsMaintenanceMode) {
this.$router.push({ path: "/maintenance" });
}
},
mounted() {
this.preloadRoutes()
}
Hope this helps!
The most of my routes are protected and require permissions to access them. When the user signed in successfully my Navbar component makes an API call and retrieves a bunch of routes the user is able to access.
After that I add all the view files matching to the routes to the navbar.
This is an example code showing the process
<template>
<div>
<router-link
v-for="navItem in navItems"
:key="navItem.title"
:to="navItem.url"
>{{ navItem.title }}</router-link>
</div>
</template>
<script>
export default {
data: function() {
return {
navItems: []
};
},
created: async function() { // Setup the router here
this.navItems = [
// !!! API CALL !!!
{
title: "Dashboard",
url: "/Dashboard"
},
{
title: "Users",
url: "/users/Users"
},
{
title: "Groups",
url: "/groups/Groups"
}
];
const routes = await this.navItems.map(async navItem => {
const { url } = navItem;
return {
path: url,
component: await import(`../views${url}.vue`)
};
});
this.$router.addRoutes(routes);
}
};
</script>
Unfortunately I get this error
Uncaught (in promise) Error: [vue-router] "path" is required in a
route configuration.
but as you can see in the example code this attribute is set. I created an sample project here
https://codesandbox.io/s/vue-routing-example-i2znt
If you call this route
https://i2znt.codesandbox.io/#/Dashboard
I would expect the router to render the Dashboard.vue file.
the routes array that you build doesn't contains your routes objects.
It's an array of promises.
you should do something like
Promise.all(routes).then(resolvedRoutes => {
this.$router.addRoutes(resolvedRoutes)
})
I use Nuxt.js and I have dynamic page /items/{id}:
<template>
<div>
<h1>Item #{{ item.id }} «{{ item.title }}»</h1>
</div>
</template>
<script>
import { api } from '../../mo/api'
export default {
asyncData({ params }) {
return api(`items/${params.id}`)
},
}
</script>
Backend API returns object {item: {id: .., title: "...", ...}}.
But if an item with specified ID not exist API returns 404 response.
And Vue crash with "[Vue warn]: Property or method "item" is not defined on the instance but referenced during render."
How can I handle 404 response?
My api.js module:
import axios from 'axios'
export function api(url) {
url = encodeURIComponent(url)
return axios
.get(`http://localhost:4444/?url=${url}`)
.then(({ data }) => {
return data
})
.catch((err) => {
// 404 catch there
})
}
Solution:
Need to read manual: https://nuxtjs.org/guide/async-data/#handling-errors
just execute error function :)
<script>
export default {
asyncData({ params, error }) {
return axios
.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
},
}
</script>
If you're using the fetch() hook, this is how it should be written
<script>
export default {
async fetch() {
try {
await fetch('https://non-existent-website.commmm')
.then((response) => response.json())
} catch (error) {
this.$nuxt.context.error({
status: 500,
message: 'Something bad happened',
})
}
},
}
</script>
More context available here: https://nuxtjs.org/announcements/understanding-how-fetch-works-in-nuxt-2-12/#error-handling
Need to read the manual: https://nuxtjs.org/guide/async-data/#handling-errors :) .
I'm building an application with JWT Login and i check if the user is logged in (when visit /) and then i redirect to Dashboard:
let routes = [
{ path: '', component: Login,
beforeEnter(to, from, next) {
if (auth.loggedIn()) {
next({ path: '/dashboard' });
} else {
next();
}
}
},
{ path: '/dashboard', component: Dashboard }
];
The Dashboard component is simple:
export default {
created() {
this.loadOrders();
},
methods: {
loadOrders() {
// Load Orders
}
},
watch: {
'$route': 'loadOrders'
},
}
If i Login, i will be redirected to /dashboard and the data is fetched.
If i'm on Dashboard (http://localhost:8080/dashboard) and i hit "refresh" on browser, this works too.
But, if i'm on this url http://localhost:8080/dashboard and i delete dashboard (so i just digit http://localhost:8080) the beforeEnter see that i'm authenticated and redirect me to /dashboard, but the data is not fetched (created, mounted etc are not called).
Why there is no data section on your Dashboard component? If you use some data (ex: loading, error, post) on template, then you need to return them in data section. Try to add that section.
example:
<template>
<div>
<div v-if="loading">
Loading...
</div>
<div v-if="!loading">
{{ error }}
</div>
<div>
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
export default {
data () {
return {
loading: false,
error: null,
post: null
}
},
created () {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData () {
this.loading = true
...
this.error = msg;
this.post = post
}
}
};
When any action is taken against an API, the server responds with relevant status.
So when you are deleting the product, you have to ignore the response from the server and then push the new path to Vue router.