[Разработка веб-сайтов, JavaScript, VueJS] Отдаем корректный код 404 в связке VUE SPA + SSR
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Есть у меня один сайт, как сейчас говорят, пет-проект. Был написан в далеком 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.vueEssentialLink.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.ЗаключениеВот, пожалуй, и все, что я хотел написать.Исходники тестового проекта закинул на гитхаб
===========
Источник:
habr.com
===========
Похожие новости:
- Доступен пакетный менеджер NPM 7.0
- [Разработка веб-сайтов, CSS, HTML] 10 современных раскладок в одну строку CSS-кода (перевод)
- [PHP, Отладка, Проектирование и рефакторинг, Разработка веб-сайтов] Я сомневался в юнит-тестах, но…
- [Ненормальное программирование, JavaScript, Графический дизайн] QR-художество
- [Разработка веб-сайтов, CSS, JavaScript, Canvas, ReactJS] 24 октября приглашаем на онлайн-митап Hot Frontend в Казани
- [JavaScript, ReactJS, Программирование, Разработка веб-сайтов] Кастомные хуки. Part 1
- [Разработка веб-сайтов, PHP, Laravel] Laravel–Дайджест (5–11 октября 2020)
- [Разработка веб-сайтов, JavaScript, Программирование, VueJS] Создание блога с помощью Nuxt Content(часть первая) (перевод)
- [JavaScript, VueJS] Vue 3 Composition API: Ref или Reactive (перевод)
- [JavaScript, Программирование] Эффектное программирование. Часть 1: итераторы и генераторы
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_vuejs, #_vue, #_ssr, #_quasar, #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_vuejs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:04
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Есть у меня один сайт, как сейчас говорят, пет-проект. Был написан в далеком 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
cd q404
quasar dev -m ssr <script>
export default { name: 'AboutMe', }; </script> <template> <q-page padding> <h1>About me</h1> </q-page> </template> const routes = [
{ path : '/', component: () => import('layouts/MainLayout'), children : [ { path: '', component: () => import('pages/Index') }, // Added: { path: 'about-me', component: () => import('pages/AboutMe') }, ], }, 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' }, }, ]; <script>
... link: { type : Object, default: null, }, ... </script> <template> <q-item clickable :to="link" > ... </q-item> </template>
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) } }) preFetch: true,
export default {
name: 'Error404', preFetch({ ssrContext }) { if (ssrContext) { return Promise.reject({ code: 404 }); } }, }; 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; } //...
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; }); preFetch() {
return Vue.axios.get('/test.json') .then(response => { console.log(response); }); }, 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> store.commit('showErrorPage', response.status);
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; } Что касается третьего пункта отдавать 404 при отсутствии запрошенной информации. Т.е. эндпойнт верный, но объекта в базе данных нет.
{
"status": false, "message": "Object not found" } Promise.reject({ code: response.status });
else if (err.code >=400 && err.code < 500) {
res.status(err.code).send(`${err.code} | ${getStatusMessage(err.code)}`); } Таким образом мы получили также возможность корректной обработки любых 4хх кодов ответа от API и в первую очередь нам конечно интересны 401 и 403.ЗаключениеВот, пожалуй, и все, что я хотел написать.Исходники тестового проекта закинул на гитхаб =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_vuejs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:04
Часовой пояс: UTC + 5