Redirect to 404 page without modifying the URL in Vue.js

Viewed 5071

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?

4 Answers

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.

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.

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.

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>
Related