113

I'm trying to redirect to 404.html on page not found using the router.beforeEach global hook, without much success (using Vueand Vue-Router 1.0):

router.beforeEach(function (transition) {
    if (transition.to.path === '/*') {
        window.location.href = '/404.html'
    } else {
        transition.next()
    }
});

This is not redirecting to page 404.html when a non-existing route is inserted, just giving me an empty fragment. Only when hard-coding some-site.com/* will it redirect to the expected some-site.com/404.html

I'm sure there's something very obvious here that I'm overlooking, but I cannot figure out what.


Please note that what I am looking for is a redirect to a new page, not a redirect to another route, which could be easily achieved by using router.redirect such as in these snippets:

router.redirect({
    '*': '404'
})

Whereas on my router.map, I could have the following:

 router.map({
    '/404': {
        name: '404'
        component: {
            template: '<p>Page Not Found</p>'
        }
    }
})
nunop
  • 2,036
  • 2
  • 20
  • 36

7 Answers7

215

I think you should be able to use a default route handler and redirect from there to a page outside the app, as detailed below:

const ROUTER_INSTANCE = new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: HomeComponent },
        // ... other routes ...
        // and finally the default route, when none of the above matches:
        { path: "*", component: PageNotFound }
    ]
})

In the above PageNotFound component definition, you can specify the actual redirect, that will take you out of the app entirely:

Vue.component("page-not-found", {
    template: "",
    created: function() {
        // Redirect outside the app using plain old javascript
        window.location.href = "/my-new-404-page.html";
    }
}

You may do it either on created hook as shown above, or mounted hook also.

Please note:

  1. I have not verified the above. You need to build a production version of app, ensure that the above redirect happens. You cannot test this in vue-cli as it requires server side handling.

  2. Usually in single page apps, server sends out the same index.html along with app scripts for all route requests, especially if you have set <base href="/">. This will fail for your /404-page.html unless your server treats it as a special case and serves the static page.

Let me know if it works!

Update for Vue 3 onward:

You'll need to replace the '*' path property with '/:pathMatch(.*)*' if you're using Vue 3 as the old catch-all path of '*' is no longer supported. The route would then look something like this:

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

See the docs for more info on this update.

Luca C
  • 461
  • 4
  • 6
Mani
  • 23,635
  • 6
  • 67
  • 54
  • 1
    I think the real answer was server side handling, as detailed in the "Please Note" section above. Your `router.beforeEach` does the same thing as in my method. Your server just needs to handle `404.html` as a special case, which you can test only in an actual server, not `vue-cli`. – Mani Oct 22 '16 at 16:30
  • Your solution worked just fine, thank you so much! Also, thank you for the hint concerning server-side handling with `router.beforeEach`. I'm using a local machine (vagrant+virtualbox) running nginx. I'll play around with the configuration and see if I can come up with a server-side solution. – nunop Oct 22 '16 at 16:53
  • As Vue Router 2.2.0, I'm using `path: '*'` in the router definition to "catch all" non-defined routes, as stated in the [docs](https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route) – Andre Ravazzi Apr 18 '20 at 19:25
46

@mani's response is now slightly outdated as using catch-all '*' routes is no longer supported when using Vue 3 onward. If this is no longer working for you, try replacing the old catch-all path with

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

Essentially, you should be able to replace the '*' path with '/:pathMatch(.*)*' and be good to go!

Reason: Vue Router doesn't use path-to-regexp anymore, instead it implements its own parsing system that allows route ranking and enables dynamic routing. Since we usually add one single catch-all route per project, there is no big benefit in supporting a special syntax for *.

(from https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes)

Luca C
  • 461
  • 4
  • 6
  • What about just replacing it with `(.*)*` as `:pathMatch` would just be the param that the match is assigned to, correct? So it could be `{ path: '/:thisIsMyCustomErrorCatchAll(.*)*', component: PathNotFound },`. I looked at the docs to confirm, and couldnt find anything to go against this. https://next.router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route – redfox05 Mar 08 '21 at 16:06
5

This answer may come a bit late but I have found an acceptable solution. My approach is a bit similar to @Mani one but I think mine is a bit more easy to understand.

Putting it into global hook and into the component itself are not ideal, global hook checks every request so you will need to write a lot of conditions to check if it should be 404 and window.location.href in the component creation is too late as the request has gone into the component already and then you take it out.

What I did is to have a dedicated url for 404 pages and have a path * that for everything that not found.

{ path: '/:locale/404', name: 'notFound', component: () => import('pages/Error404.vue') },
{ path: '/:locale/*', 
  beforeEnter (to) {
    window.location = `/${to.params.locale}/404`
  }
}

You can ignore the :locale as my site is using i18n so that I can make sure the 404 page is using the right language.

On the side note, I want to make sure my 404 page is returning httpheader 404 status code instead of 200 when page is not found. The solution above would just send you to a 404 page but you are still getting 200 status code. To do this, I have my nginx rule to return 404 status code on location /:locale/404

server {
    listen                      80;
    server_name                 localhost;

    error_page  404 /index.html;
    location ~ ^/.*/404$ {
      root   /usr/share/nginx/html;
      internal;
    }

    location / {
      root   /usr/share/nginx/html;
      index  index.html index.htm;
      try_files $uri $uri/ @rewrites;
    }

    location @rewrites {
      rewrite ^(.+)$ /index.html last;
    }

    location = /50x.html {
      root   /usr/share/nginx/html;
    }
}
Alan Ho
  • 646
  • 9
  • 5
  • The issue with this leads to the 200 status code for 404 page, Which is not acceptable according to the SEO perspective. Can anyone suggest how to set the 404 status code in VueJS for 404 page? – Jenil Kanani Dec 08 '20 at 09:12
4

Do not use redirect; use beforeEnter

const router = [

    // { path: '*', redirect: '/404'},
    { path: '*', beforeEnter: (to, from, next) => { next('/404') } },
    {
        path: '/404',
        name: '404',
        component: () => import('@/views/404')
    }
]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
anson
  • 1,436
  • 14
  • 16
3

path: "*" is outdated. It belongs to vue-router2 on vue-router3. You can catch all 404 error pages by using

/:pathMatch(.*)*

It works well.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gabriel soft
  • 432
  • 3
  • 7
1

This will make it work too:

{
    path: '/*/',
    name: 'PageNotFound',
    component: () => import('../views/PageNotFound.vue')
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

I do not specifically answer your question, but I have an answer for anyone who is looking for page not found route syntax in Vue js just like I was looking for and came here.

Here are two things you can do:

  1. First, create a component in the views and write some HTML like error 404!

  2. Then import that component in index.js in router folder and import it there like:

  3. import yourcomponentName from '../views/yourcomponentName.vue'

  4. const routes = [ { path: '/:catchAll(.*)', ->This is the regex pattern name: 'whatever name you want to give', component: yourComponentName }]

  5. If it does not work then use this

  6. const routes = [ { path: '/:pathMatch(.*)', ->This is the regex pattern name: 'whatever name you want to give', component: yourComponentName }]

    Remove the comment: 'This is the regex pattern it is writtern for understanding'