5

In my Vue.js project, I want to display my 404 page, if a route parameter is invalid. For now, I'm using the following code for that:

this.$router.replace({ path: '/404' });

Is there a way to do that without modifying the URL? I want the user to still be able to copy the browser's original URL line. Is there some kind of a silent: true parameter?

2
  • Might be helpful: github.com/vuejs/vue-router/issues/977 Commented Mar 4, 2020 at 10:44
  • @FarkhatMikhalko Unfortunately no. I already found that discussion. In my case, the redirect should be initiated from the view itself. Commented Mar 4, 2020 at 10:52

4 Answers 4

8

This was fixed in Vue Router 4 which you can see on the second example in the docs.

Build your NotFound route like this:

{ 
  path: '/:pathMatch(.*)*', 
  name: 'NotFound', 
  component: NotFound 
},

Then you can use a beforeEnter navigation guard on your dynamic Vue like so:

// In your router/index.js file...
{
  path: 'users/:id',
  name: 'User Detail',
  component: UserDetail,
  beforeEnter(to, from) {
    // See if that query exists in your data...
    const exists = data.users.find(
      user => user.id === parseInt(to.params.id)
    )
    if (!exists) {
      // THE IMPORTANT PART
      // Return your not found view...
      return {
        name: 'NotFound',
        // Match the path of your current page and keep the same url...
        params: { pathMatch: to.path.split('/').slice(1) },
        // ...and the same query and hash.
        query: to.query,
        hash: to.hash,
      }
    }
  }
}

Haven't tested this in a Component yet, but I'd assume it'd be the same logic in the beforeRouteEnter navigation guard.

Sign up to request clarification or add additional context in comments.

2 Comments

This does not give you the 404 status code right?
I was trying to do something similar to your code, but I was getting the URL with %F2 because of the "/" character. Now with some of your code I can preserve for a dynamic URL for the 404 page. next({ name: 'NotFound', params: { pathMatch: to.path.split('/').slice(1) }, query: to.query, hash: to.hash })
7

With vue-router, the URL is the source of truth. If the URL changes, so does the rendering. You can't "pause" the router. (This is a flaw in vue-router that has been bugging me for ages, but I digress.)

You just have to display the 404 page without modifying the route. Have some display404 data property in your root component that you can set to display the 404 page manually in the template instead of the <router-view>, e.g:

<div>
  <my-404-page v-if="display404"/>
  <router-view v-else/>
</div>

To display the 404 page from any component:

this.$root.display404 = true

Of course this is just a basic example to demonstrate what I mean, you might want to use Vuex to share the state, or use an event bus, or you can display the 404 page in some other way that works for you, etc.

8 Comments

Hmmmm, looks very hacky, but at least it's a solution. I wonder if this is really such a special requirement that there is no widely spread standard functionality for it.
There is no (official) way to make vue-router display a certain route without changing the URL. vue-router cannot get into a state where the view does not reflect the URL.
I see. Would it be possible to replace the content inside the <template> tag of my specific sub page instead of modifying the global layout?
That would result in the 404 page being displayed inside your sub page, is this what you want?
That would be perfectly fine. I already tried this approach: <template><div v-if="clientSettings">...</div><div v-else>How can I get the error page in here?</div></template>.
|
0

Not 100% sure what you are asking, but is either of these any help?

A catch all route: From Vue.js docs "Catch all route"

Or if you are managing a response form a call (method/fetch/ etc): Use a combination of try/catch and a "loading" data value to change the display or what component is loaded.

3 Comments

I already implemented that. But I also want to redirect to the 404 page defined there in some cases, if an ajax call inside of one of my views goes wrong. Something like axios.get('someData').then(d => doSomething()).catch(err => display404Page());.
Go to amazon.com/asdf and notice that it displays the 404 page without changing the root. That's what we're looking for.
Could you pass the router into the display 404 page function? display404Page(this.$router) Or let the error 'bubble up' to wherever the axios.get function is being called? E.g. Store action or component method. Then deal with it, it what i do and then keeps the axios script clean. It also lets you separate concerns
0

Based on Decade Moon's solution, I did the following:

main.js

import Error404 from './views/error/404.vue'

Vue.component('error-404', Error404)

404.vue

<template>
    <div>
        <h1>Page not found</h1>
        <p>Whatever...</p>
    </div>
</template>

<script>
    export default {
        name: 'Page not found'
    }
</script>

router --> index.js

const PageNotFound = () => import('@/views/error/404')

function configRoutes() {
    return [
        {
            path: '/',
            name: 'Home',
            component: TheContainer,
            children: [
                // ...
                {
                    path: '404',
                    name: 'Page not found',
                    component: PageNotFound,
                    alias: '*'
                }
            ]
        }
    ]
}

My Page which should display the 404 error

<template>
    <div class="animated fadeIn" v-if="clientSettings">
        ...
    </div>
    <error-404 v-else></error-404>
</template>

<script>
    export default {
        name: 'Test',
        data() {
            return {
                clientSettings: null
            };
        },
        async created() {
            this.setClientConfig();
        },
        watch: {
            '$route.params.id': function (id) { this.setClientConfig(id);}
        },
        methods: {
            setClientConfig(id) {
                if (!id) {
                    id = this.$route.params.id;

                    // Redirect to the first valid list, if no parameter is proviced
                    if (!id) {
                        this.$router.push({ name: 'Test', params: { id: this.$root.clientConfiguration[0].name } });
                        return;
                    }
                }

                // Set client settings
                this.clientSettings = this.$root.clientConfiguration.find(cc => cc.name === id);
                // This will return null, if no entry was found, therefore the template will jump into the v-else
            }
        }
    }
</script>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.