Vue router 404 ошибка

In the routes declaration, I like to add this:

[
  ...  
  { path: '/404', component: NotFound },  
  { path: '*', redirect: '/404' },  
  ...  
]

Which will imply that if the user is navigated to a path which does not match any routes, it will be redirected to the «404» route, which will contain the «not found» message.

The reason I’ve separated it into 2 routes is so that you can also programmatically direct the user to the 404 route in such a case when some data you need does not resolve.

For instance, if you were creating a blog, you might have this route:

{ path: '/posts/:slug', component: BlogPost }

Which will resolve, even if the provided slug does not actually retrieve any blog post. To handle this, when your application determines that a post was not found, do

return this.$router.push('/404')

or

return router.push('/404')

if you are not in the context of a Vue component.

One thing to bear in mind though is that the correct way to handle a not found response isn’t just to serve an error page — you should try to serve an actual HTTP 404 response to the browser. You won’t need to do this if the user is already inside a single-page-application, but if the browser hits that example blog post as its initial request, the server should really return a 404 code.

# Динамические пути

Очень часто нам требуется сопоставить маршруты с заданным шаблоном с одним и тем же компонентом. Например, у нас может быть компонент User, который должен отображаться для всех пользователей, но с разными ID пользователей. Во vue-router мы можем использовать динамический сегмент в маршруте, чтобы достичь этого:

Теперь все URL вида /user/foo и /user/bar будут соответствовать одному маршруту.

Динамический сегмент обозначается двоеточием :. При сопоставлении маршрута, значение динамического сегмента можно получить через this.$route.params в каждом компоненте. Теперь мы можем отобразить ID текущего пользователя, обновив шаблон компонента User:

Вы можете посмотреть этот пример вживую здесь (opens new window).

Может быть несколько динамических сегментов в одном маршруте. Для каждого сегмента появится соответствующее свойство в $route.params. Например:

Шаблон Совпадающий путь $route.params
/user/:username /user/evan { username: 'evan' }
/user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: '123' }

Кроме $route.params, объект $route предоставляют и другую полезную информацию, например $route.query (если URL содержит строку запроса), $route.hash, и т.д. Подробнее в справочнике API.

# Отслеживание изменений параметров

Важно отметить, что при переходе от /user/foo к /user/bar будет повторно использован тот же самый экземпляр компонента. Поскольку оба маршрута указывают на один и тот же компонент, этот подход эффективнее, чем уничтожение и повторное создание экземпляра. Но это означает, что хуки жизненного цикла компонента при этом вызываться не будут.

Чтобы реагировать на изменения параметров маршрута в рамках одного компонента, достаточно просто отслеживать изменения в объекте $route:

Или можно воспользоваться хуком beforeRouteUpdate, добавленным в версии 2.2:

# Страница ошибки 404 / отслеживание ненайденных путей

Обычные параметры соответствуют символам между фрагментами URL, разделёнными /. При необходимости чтобы совпадало что угодно можно воспользоваться звёздочкой (*):

Если используете маршруты со звёздочкой, убедитесь в их правильном порядке, чтобы они были в конце.
Маршрут { path: '*' } обычно используют для страницы ошибки 404 на стороне клиента. При использовании Режима HTML5 History также проверьте что правильно сконфигурировали сервер.

При наличии звёздочки в $route.params автоматически добавляется свойство pathMatch. Оно будет содержать оставшуюся часть URL-адреса, сопоставленную со звёздочкой:

# Продвинутые возможности сопоставления

vue-router использует path-to-regexp (opens new window) в качестве движка для проверки совпадения маршрутов, что позволяет задействовать многие продвинутые возможности, включая опциональные динамические сегменты и регулярные выражения. Подробнее об этих продвинутых возможностях можно изучить в документации библиотеки (opens new window), а на примере (opens new window) узнать как использовать их совместно с vue-router.

# Приоритеты при сопоставлении маршрутов

Иногда один и тот же URL может совпасть с несколькими маршрутами. В таких случаях приоритет определяется порядком определения маршрутов: чем раньше определён маршрут, тем выше у него приоритет.

If you are reading this post, chances are that you have been trying to implement a catch-all route into Vue 3, or upgrading one from Vue 2, and not being able to succeed. This article is going to provide you with the details you need to upgrade your application and successfully set up a route that would catch all endpoints.

TLTR;

Vue-router 4 (the version of vue-router that works with Vue 3) introduced a breaking change that require you to define a star or catch-all route using a parameter with a custom regex. An example for a 404 route would be “{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound }“.

What is a catch all route

A catch all route, also known as a wildcard, is a route that is specifically set to redirect users to a specific page in the case in which the inserted URL is not existing. This wildcard is very commonly used for a 404 page.

Regex in catch all route

Routes are usually defined by creating rules on the specific URL path. So for example creating a route for a path of /user would be caught when the user enters website.com/user, but it won’t if the user enters website.com/my-user or any other word that is not precisely the one set in the path rule.

For the case of a wildcard, or catch all route the rule is usually set using a regular expression. A regular expression is:

regular expression (shortened as regex or regexp; sometimes referred to as rational expression) is a sequence of characters that specifies a search pattern in text

https://en.wikipedia.org/wiki/Regular_expression

For the case of a catch all, the router rule would be defined by using a regex that would catch all character. This would be defined using the two symbols .*. These two symbols means respectively:

  • . – indicates any character
  • * – mean “0 or more instances of the preceding regex token”

How do you define a catch all in Vue 3

Now that we have learned what a catch all actually is, it is time to see how to use this in practice in our Vue 3 application. As I mentioned at the very start of this post, the syntax required to define this route in Vue 3 has changes from Vue 2 due to a breaking change.

The main change in Vue 3 require us to setup the Regex using a parameter with custom regex.

What are parameters with custom regex

Before we move forward it is important to really understand what a parameters with custom regex are in Vue router. Many people, included me, would jump to the definition of the catch all rule, without actually understanding what a parameter with custom regex is and its usage.

As the name explain, this feature merges two important options of routes: parameters and regex.

Parameters

In the site expressjs.com parameters in route are expressed as:

Route parameters are named URL segments that are used to capture the values specified at their position in the URL

https://expressjs.com

So for example a route set with the following path /:orderId would capture any value inserted after the forward slash and provide it to the server. Please note that the name used for the parameter is completely arbitrary and we could have used whatever we want as it does not affect the URL accessed, as shown below:

// Routes
{ path: '/:arbitraryParameter' }

// Correct URL
mywebsite.com/123
mywebsite.com/simone
mywebsite.com/simone123

Regex

The next features offered by the parameter with custom regex is the ability to define a regex for our route. So for example, we could enhance the example above by making sure that the route is just hit if the orderId is made of all numbers:

{ path: '/:orderId(\\d+)' }

Using a bunch of custom regex to define paths is not really advised, as custom regex can be very fragile and hard to maintain. To avoid complicated routes, it is suggested to created nested routes to easily differentiate between routes without the need of a custom regex.

{ path: '/order/:orderId' }, // accessed from mywebsite.com/order/123
{ path: '/user/:userId' } // accessed from mywebsite.com/user/simone

The wildcard (catch all) scenario

Even if above we have indicated that regex should to not be used, there are some special cases where the use of regex can create helpful feature, for example when creating a catch all or 404 route.

As we mentioned above, this route is used in the case in which the user has miss-typed a route and did not enter anything that exist on the site, and returns a 404 (not found) page.

In this scenario, the use of a regex is beneficial as it helps us to define a rule that would catch URL entered, by creating a very generic regex.

To define a catch all rule in vue-router 4 we would enter the following code:

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

As we can see, the catch all route is defined using the parameter with custom regex that we have recently introduced. The above rule will work with any URL that cannon be found in our defined rules, so for example mywebsite.com/fake/url and mywebsite.com/404 will redirect correctly to the NotFound component, but unfortunately it wont work if we try to call our route by its name.

How to create a catch all route that can be called by its name

There are cases in which the developers may want to redirect an user to the 404 page dynamically. This is usually the result of a bad request or an action that has resulted in the wrong outcomes. When defining a programmatic navigation, it is usually beneficial to do so using the name of routes, as it makes the code more readable. For example in our case the code to programmatically navigate to a catch all route would look like this:

router.push({ name: 'not-found'});

As we quickly mentioned previously, the above programmatic navigation will not work with out defined route. This is because the parameter that we have defined in our not-found route is currently required and it cannot be omitted.

To be able to navigate to this route with the use of its name (and without having to pass extra parameter) we need to make our route parameter optional.

Paramethers can be made optional in 2 different ways:

  • ? Mark a parameter as optional and cannot be repeated
  • * Mark a parameter as optional but repeatable.

In our case we would like to receive a list of all parameters sent as part of the URL and we would therefore use the *. Our route will now look like this:

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

With the above code, we are not only able to catch wrong URL, but also able to dynamically access the route.

Conclusion

Creating a catch all route is quite simple, and searching on the internet usually provided the answer that we all wanted, but it is important sometimes to investigate things further and try to understand how things actually work and the main reason behind specific features.

In my case fully understand the catch all, has helped me to fully comprehendthe parameter with custom regex and “optional” options.

I hope this post has been helpful to you as it has been to me, and please feel free to leave a comment to help me improve this post for future readers.

Related

In this article, we will look at how to implement a catch-all route to handle the page not found error and display a custom 404 page in the Vue application.

In the router configuration, we can specify a path /:notFound and also define a NotFound component to display the 404 custom page.

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      // ...
    },
    {
      path: "/:notFound",
      component: NotFound,
    },
  ],
});

The /:notFound will match any string in the URL and display the NotFound component. Let’s implement it and see it in action.

We will begin by creating a new application using Vue CLI.

❯ vue create catch-all-demo

Once, the installation is complete, we can navigate to the folder and run the server to load the application.

❯ cd catch-all-demo
❯ npm run serve

This will run our server on port 8080 by default http://localhost:8080/

Next, we will install the vue-router package required for routing. We can install it using CDN, npm or yarn.

npm:

npm install vue-router@4

yarn:

yarn add vue-router@4

Once the package is installed, we can integrate it with our Vue application.

Let’s create two components for our demo purpose.

❯ touch src/components/MyComponent.vue
❯ touch src/components/NotFound.vue

We will open the files and add some template code.

<!-- MyComponent.vue -->

<template>
  <h2>MyComponent</h2>
</template>

NotFound.vue

<!-- NotFound.vue -->

<template>
  <h2>404 Not found. The page you are looking for does not exist.</h2>
</template>

In the main.js we can define two routes, one route will be to display the MyComponent and the second route will be the catch-all route which will display the NotFound component.

// main.js

import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue";

import MyComponent from "./components/MyComponent";
import NotFound from "./components/NotFound";

const app = createApp(App);

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: "/my-component",
      component: MyComponent,
    },
    {
      path: "/:notFound",
      component: NotFound,
    },
  ],
});

app.use(router);
app.mount("#app");

Here, we have defined the catch-all route /:notFound which will catch any string that does not match the predefined route paths and display the NotFound component.

In the App.vue let’s add the router view.

<!-- App.vue -->
<template>
  <router-view></router-view>
</template>

<script>
export default {
  name: "App",
};
</script>

With this let’s open up the URL http://localhost:8080/my-component

catch-all-404-routes-vue-1

As expected, the Mycomponent component gets displayed. Now let’s try to add some random path in the URL like http://localhost:8080/xxx or http://localhost:8080/something

catch-all-404-routes-vue-2

catch-all-404-routes-vue-3

As you can see, our catch route is working and is able to display the custom 404 content.

Время на прочтение
6 мин

Количество просмотров 6K

Есть у меня один сайт, как сейчас говорят, пет-проект. Был написан в далеком 2013 году, что называется «на коленке» без использования каких-то фреймворков. Только php, только хардкор. Но тем не менее, функции свои выполнял, даже обрел некую популярность в узких кругах и был неплохо проиндексирован.

Недавно было решено начисто переписать его на современном стеке. Выбор пал на Laravel и Vue с серверным рендером. Сказано — сделано. Сайт переписан, развернут на vps, работает. Но есть одно но. В яндекс-метрике остались тысячи ссылок, которые на текущий момент не актуальны, но эти адреса возвращают код 200 и поисковый бот снова и снова их проверяет. Не хорошо.

Итак, проблема обозначена. Посмотрим на используемый стек технологий, чтобы понять что к чему.

Подготовка

Laravel используется исключительно в качестве API, на сервере висит на localhost:81, а nginx проксирует к нему маршруты /api . Здесь ничего не сделать.

Фронтэнд написан с использованием фреймворка quasar. Это невероятно крутая вещь, которая может собрать вам сайт или приложение под несколько платформ. Я использую платформу SSR. В этом случае квазар собирает весь фронт, плюс генерирует nodejs-сервер на базе express. Этот сервер у меня запущен на localhost:3000 и опять же nginx проксирует к нему все остальные запросы (кроме API).

Чтобы говорить более предметно, давайте создадим простенький проект. Будем считать, что с установкой quasar/cli вы справитесь сами/

quasar create q404

В папке q404 будет создана стартовая заготовка проекта. Можно перейти в нее и запустить сервер разработки.

cd q404
quasar dev -m ssr

Не заморачиваясь сильно на этом тестовом проекте, добавим вторую страницу AboutMe:

pages/AboutMe.vue

<script>
export default {
  name: 'AboutMe',
};
</script>
<template>
  <q-page padding>
    <h1>About me</h1>
  </q-page>
</template>

Соответствующий роут

router/routes.js

const routes = [
  {
    path     : '/',
    component: () => import('layouts/MainLayout'),
    children : [
      { path: '', component: () => import('pages/Index') },
      // Added:
      { path: 'about-me', component: () => import('pages/AboutMe') },
    ],
  },

И заменим главное меню

layouts/MainLayout.vue

const linksData = [
  {
    title: 'Homepage',
    icon : 'code',
    link : { path: '/' },
  },
  {
    title: 'About Me',
    icon : 'code',
    link : { path: '/about-me' },
  },
  {
    title: '404 test',
    icon : 'code',
    link : { path: '/404' },
  },
];

Для правильной работы следует еще поменять компонент EssentialLink.vue

EssentialLink.vue

<script>
   ...
   link: {
      type   : Object,
      default: null,
   },
   ...
</script>
<template>
  <q-item
      clickable
      :to="link"
  >
  ...
  </q-item>
</template>

Теперь все готово. Если сейчас мы запустим dev-сервер и откроем сайт, то увидим, что все работает, а заглянув в исходный код страницы убедимся, что и серверный рендер отрабатывает.

Кроме одной проблемы — страница 404 возвращает нам код ответа 200.

Поиск решения

Поиск информации в интернете готовых к использованию решений не дал. В официальном репозитории квазара есть ишью где рекомендуют создать отдельный роут для 404 страницы и редиректить на нее. Это не всегда подходит, мне, например, хотелось бы, чтобы пользователь оставался на той же странице, которую запросил, но с отображением плашки «404 not found», т.е. чтобы url в адресной строке не менялся.

Другой совет заключается в том, чтобы не использовать в приложении роут «*». Да. В этом случае при запросе несуществующей страницы сервер ответит кодом 404, но при навигации внутри приложения у нас теперь не будет красивой плашки 404. Не подходит.

На тостере предлагалось в сервере express делать дополнительные запросы и отправлять при необходимости 404-й код. Но сами понимаете, такое себе решение. В топку.

Встречались и еще советы разной степени полезности, но все они были отброшены как не подходящие по тем или иным причинам.

Но как я люблю отвечать заказчикам на их хотелки — «для программиста нет ничего невозможного, чего бы он не мог сделать с кодом». Это наша вселенная, мы здесь боги.

Решение

Давайте еще раз сформулируем ТЗ. Мы хотим

  • отдавать 404 по несуществующим адресам (тем, что явно не прописаны в нашем роутере)

  • отдавать 404 по несуществующим эндпойнтам API

  • отдавать 404 при отсутствии запрошенной информации. Т.е. эндпойнт верный, но объекта в базе данных нет.

  • также не хотим отказываться от использования роута «*» на стороне клиента

Решение на самом деле находится на поверхности.

Посмотрим на на код сервера, который нам предлагает квазар:

src-ssr/index.js

ssr.renderToString({ req, res }, (err, html) => {
    if (err) {
      if (err.url) {
        res.redirect(err.url)
      }
      else if (err.code === 404) {
        // Should reach here only if no "catch-all" route
        // is defined in /src/routes
        res.status(404).send('404 | Page Not Found')
      }
      else {
        // Render Error Page or
        // create a route (/src/routes) for an error page and redirect to it
        res.status(500).send('500 | Internal Server Error')
        if (ssr.settings.debug) {
          console.error(`500 on ${req.url}`)
          console.error(err)
          console.error(err.stack)
        }
      }
    }
    else {
      res.send(html)
    }
})

Комментарий в ветке условия 404 предупреждает нас, что сюда мы попадем, только если не будем использовать роут «*». А также мы можем понять, что если фреймворк не выбрасывает нас сюда, то мы сами можем бросить ошибку с телом {code:404}.

Квазар предлагает нам дополнительный хук — preFetch. Мы можем им воспользоваться, чтобы на стороне сервера выбросить нужную ошибку.

Для задействования данной фичи, нужно раскомментировать в файле quasar.conf.js строку

preFetch: true,

В компоненте Error404.vue добавим код

export default {
  name: 'Error404',
  preFetch({ ssrContext }) {
    if (ssrContext) {
      return Promise.reject({ code: 404 });
    }
  },
};

Теперь при отображении данного компонента будет выбрасываться ошибка и express сервер сможет поймать её и ответить кодом 404. Причем, ошибка будет выбрасываться только в контексте серверного рендера, на клиенте же, перейдя на не существующий адрес, мы увидим красивую заглушку NotFound.

Первый и четвертый пункты требований мы выполнили.

Теперь займемся обработкой api-вызовов. Подготовим Axios. Создадим инстанс, настроим его и привяжем Vue.

boot/axios.js

import Axios from 'axios';

export default ({ Vue, ssrContext, store }) => {

  let axiosInstance = Axios.create({
    baseURL         : '/api',
    timeout         : 0,
    responseType    : 'json',
    responseEncoding: 'utf8',
    headers         : {
      'X-Requested-With': 'XMLHttpRequest',
      'Accept'          : 'application/json',
    },

    // Reject only if the status code is greater than or equal to specify here
    validateStatus: status => status < 500,
  });
  
  // ...

  Vue.axios = axiosInstance;
}

Здесь все стандартно — обозначаем базовый урл, типы ответов, кодировку, заголовки. Функция validateStatus определяет ответы с какими кодами считать ошибкой. Мы будем считать ошибками все коды 5xx. В этом случае сайт будет возвращать код 500 и соответствующее сообщение.

Чтобы централизованно обрабатывать запросы к несуществующим эндпойнтам, добавим в эту конфигурацию перехватчик (interceptor в axios):

//...
axiosInstance.interceptors.response.use(response => {
  if (response.status >= 400) {
    if (ssrContext) {
      return Promise.reject({ code: response.status });
    } else {
      // store.commit('showErrorPage', response.status);
    }
  }
  return response.data;
});

К закомментированной строке вернемся позднее. Теперь Axios будет отклонять промис при ответах сервера с ошибками 4xx, и мы будем попадать в соответствующую ветку условия в сервере express чтобы вернуть правильный статус-код.

Для примера модифицируем компонент AboutMe.vue, добавив в него запрос к нашему API. Так как апишки у нас сейчас нет, запрос вернет 404 ошибку.

preFetch() {
  return Vue.axios.get('/test.json')
    .then(response => {
      console.log(response);
    });
},

Здесь два важных момента. Мы должны обязательно вернуть промис и мы не должны перехватывать ошибку, оставив это на откуп библиотеке Axios. Если нам нужно выполнить для данной страницы несколько запросов, можно обернуть их в Promise.all.

Теперь, если мы перейдем на адрес /about-me, и обновим страницу, то увидим в панели разработчика браузера, что запрос страницы возвращает ответ с кодом 404. То что нужно поисковым системам! Пункт два выполнен.

Однако при внутреннем переходе на данную страницу пользователь никак не информируется о проблеме. Тут можно применить разные решения для отображения плашки 404. Я использовал следующее.

Добавил в стор флаг

showErrorPage: false,

Мутацию

export const showErrorPage = (state, show) => state.showErrorPage = show;

И условие в компонент основной раскладки

<q-page-container>
  <Error404 v-if="$store.state.example.showErrorPage"/>
  <router-view v-else/>
</q-page-container>

И возвращаясь к загрузчику Axios, раскомментируем там строку

store.commit('showErrorPage', response.status);

Еще в роутере придется добавить хук beforeEach для сброса этого флага (но только при работе в браузере)

router/index.js

export default function ({ store, ssrContext }) {
  const Router = new VueRouter({
    scrollBehavior: () => ({ x: 0, y: 0 }),
    routes,
    mode: process.env.VUE_ROUTER_MODE,
    base: process.env.VUE_ROUTER_BASE,
  });

  if (!ssrContext) {
    Router.beforeEach((to, from, next) => {
      store.commit('showErrorPage', false);
      next();
    });
  }

  return Router;
}

На данный момент мы реализовали 3 из 4-х пунктов технического задания.
Что касается третьего пункта

отдавать 404 при отсутствии запрошенной информации. Т.е. эндпойнт верный, но объекта в базе данных нет.

то тут возможны варианты. Если вы делаете свой API, как положено, RESTful, то такой запрос обязан вернуть статус-код 404, что уже вписывается в построенную систему. Если же вы по каким-то причинам возвращаете объекты типа

{
  "status": false,
  "message": "Object not found"
}

то можно добавить дополнительные проверки в перехватчик Axios.

Еще кое-что

Внимательный читатель заметил, что мы в перехватчике отклоняем промис таким образом:

Promise.reject({ code: response.status });

А значит должны немного доработать express-сервер

else if (err.code >=400 && err.code < 500) {
  res.status(err.code).send(`${err.code} | ${getStatusMessage(err.code)}`);
}

Реализацию функции getStatusMessageрассматривать не будем =)
Таким образом мы получили также возможность корректной обработки любых 4хх кодов ответа от API и в первую очередь нам конечно интересны 401 и 403.

Заключение

Вот, пожалуй, и все, что я хотел написать.

Исходники тестового проекта закинул на гитхаб

Понравилась статья? Поделить с друзьями:
  • Vue cli ошибка при установке
  • Vts ошибка а57
  • Vts ошибка а022
  • Vts ошибка al41
  • Vts ошибка al00