Как обновить заголовок страницы и метаданные с помощью Vue.js и vue-router
Введение
vue-router — отличное решение для маршрутизации для Vue.js, но требует дополнительной настройки для обновления заголовка страницы и метаданных при изменении маршрута. Будут времена, когда вы захотите, чтобы заголовок браузера менялся при изменении страницы. А для SEO (поисковая оптимизация) вам не нужно, чтобы в каждом результате поиска или ссылке на ваш веб-сайт было указано «Домашняя страница» для всех маршрутов.
В этой статье вы узнаете, как добавить эту функцию самостоятельно. Вы создадите пример приложения Vue с настраиваемым заголовком страницы и метаданными при изменении маршрута.
Предпосылки
Для выполнения этого урока вам понадобятся:
- Node.js установлен локально, что можно сделать, следуя инструкциям по установке Node.js и созданию локальной среды разработки.
Это руководство было проверено с помощью Node v14.6.0, npm v6.14.7, Vue.js v2.6.11, vue-router
v3.2.0 и @vue/cli
v4. 4.6.
Шаг 1 — Создание проекта Vue и установка зависимостей
Давайте создадим новый проект Vue.
Сначала откройте терминал и с помощью vue-cli
создайте проект Vue:
- npx @vue/cli@4.4.6 create --inlinePreset='{ "useConfigFiles": false, "plugins": { "@vue/cli-plugin-babel": {}, "@vue/cli-plugin-eslint": { "config": "base", "lintOn": ["save"] } }, "router": true, "routerHistoryMode": true }' vue-router-meta-example
Эта длинная команда представляет собой набор предустановок на основе значений по умолчанию, установленных @vue/cli/packages/@vue/cli/lib/options.js
. При переформатировании для удобочитаемости это выглядит так:
{
"useConfigFiles": false,
"plugins": {
"@vue/cli-plugin-babel": {},
"@vue/cli-plugin-eslint": {
"config": "base",
"lintOn": ["save"]
}
},
"router": true,
"routerHistoryMode": true
}
Эти пресеты добавляют vue-router
в качестве подключаемого модуля (cli-plugin-router
), включают режим истории, добавляют Babel и добавляют ESLint.
Для нужд этого руководства вам не потребуются TypesScript, поддержка Progressive Web App (PWA), Vuex, препроцессоры CSS, модульное тестирование или сквозное (E2E) тестирование.
Затем перейдите в новый каталог проекта:
- cd vue-router-meta-example
На данный момент у нас есть свежий проект Vue, на который можно опираться. Следующим шагом будет определение пробных маршрутов в приложении. Как только мы установим структуру нашего приложения, мы сможем увидеть изменения title
и meta
в действии.
Шаг 2 — Определение пробных маршрутов и шаблонов
В нашем примере нашей целью будет создание приложения, состоящего из:
- домашний маршрут (
/
) - соседний маршрут About (
/about
) - и вложенный маршрут часто задаваемых вопросов (
/about/frequently-asked-questions
)
Теперь откройте main.js
:
- nano src/main.js
Найдите минутку, чтобы ознакомиться с тем, как VueRouter
был добавлен с помощью cli-plugin-router
:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
Теперь откройте router/index.js
:
- nano src/router/index.js
Найдите минутку, чтобы ознакомиться с маршрутами для Home
и About
, сгенерированными cli-plugin-router
. И добавьте маршрут для вложенных Часто задаваемых вопросов
:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import FrequentlyAskedQuestions from '../views/FrequentlyAskedQuestions.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
children: [
{
path: 'frequently-asked-questions',
component: FrequentlyAskedQuestions,
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
Это устанавливает нашу желаемую маршрутизацию для этого руководства. Обратите внимание, что мы ссылаемся на представление, которое еще не существует. Мы рассмотрим это далее.
Создайте новый файл с именем FrequentlyAskedQuestions.vue
в каталоге views
:
- nano src/views/FrequentlyAskedQuestions.vue
Затем добавьте шаблон:
<template>
<div>
<h2>Frequently Asked Questions</h2>
<dl>
<dt>What is your favorite aquatic animal?</dt>
<dd>Sharks.</dd>
<dt>What is your second favorite aquatic animal?</dt>
<dd>Dolphins.</dd>
<dt>What is your third favorite aquatic animal?</dt>
<dd>Cuttlefish.</dd>
</dl>
</div>
</template>
<style>
dt {
font-weight: bold;
}
dd {
margin: 0;
}
</style>
У нас есть новое представление, но нам все еще нужно сослаться на него в приложении.
Теперь откройте About.vue
:
- nano src/views/About.vue
Затем добавьте
, чтобы вложенные маршруты отображали children
;
<template>
<div class="about">
<h1>This is an about page</h1>
<router-view/>
</div>
</template>
Теперь откройте App.vue
:
- nano src/App.vue
Найдите минутку, чтобы ознакомиться с тем, как файл изменяется с помощью cli-plugin-router
. И добавьте
для Часто задаваемых вопросов
:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/about/frequently-asked-questions">FAQ</router-link>
</div>
<router-view/>
</div>
</template>
На данный момент у нас есть приложение Vue с маршрутами к Главная
, О программе
и Часто задаваемые вопросы
. Мы можем запустить следующую команду:
- npm run serve
И посетите localhost:8080
в веб-браузере. При нажатии на навигационные ссылки должны отображаться ожидаемые компоненты. Однако теги <title>
и <meta>
пока не меняются.
Шаг 3 — Добавление метаполей маршрута и навигационной защиты
vue-router
поддерживает метаполя маршрута для значений title
и meta
. Давайте вернемся к нашим маршрутам и добавим метаполя.
Откройте router/index.js
:
- nano src/router/index.js
И добавьте поля meta
для Главная
, О нас
и Часто задаваемые вопросы
:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import FrequentlyAskedQuestions from '../views/FrequentlyAskedQuestions.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: {
title: 'Home Page - Example App',
metaTags: [
{
name: 'description',
content: 'The home page of our example app.'
},
{
property: 'og:description',
content: 'The home page of our example app.'
}
]
}
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
meta: {
title: 'About Page - Example App',
metaTags: [
{
name: 'description',
content: 'The about page of our example app.'
},
{
property: 'og:description',
content: 'The about page of our example app.'
}
]
},
children: [
{
path: 'frequently-asked-questions',
component: FrequentlyAskedQuestions,
meta: {
title: 'Nested - About Page - Example App'
}
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
Однако это не приведет к обновлению заголовка страницы и метаданных при изменении маршрута.
Для этого нам понадобится специальный навигационный сторож.
В файле route/index.js
добавьте глобальную защиту навигации после маршрутов, но перед экспортом router
:
// ...
// This callback runs before every route change, including on page load.
router.beforeEach((to, from, next) => {
// This goes through the matched routes from last to first, finding the closest route with a title.
// e.g., if we have `/some/deep/nested/route` and `/some`, `/deep`, and `/nested` have titles,
// `/nested`'s will be chosen.
const nearestWithTitle = to.matched.slice().reverse().find(r => r.meta && r.meta.title);
// Find the nearest route element with meta tags.
const nearestWithMeta = to.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
const previousNearestWithMeta = from.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
// If a route with a title was found, set the document (page) title to that value.
if(nearestWithTitle) {
document.title = nearestWithTitle.meta.title;
} else if(previousNearestWithMeta) {
document.title = previousNearestWithMeta.meta.title;
}
// Remove any stale meta tags from the document using the key attribute we set below.
Array.from(document.querySelectorAll('[data-vue-router-controlled]')).map(el => el.parentNode.removeChild(el));
// Skip rendering meta tags if there are none.
if(!nearestWithMeta) return next();
// Turn the meta tag definitions into actual elements in the head.
nearestWithMeta.meta.metaTags.map(tagDef => {
const tag = document.createElement('meta');
Object.keys(tagDef).forEach(key => {
tag.setAttribute(key, tagDef[key]);
});
// We use this to track which meta tags we create so we don't interfere with other ones.
tag.setAttribute('data-vue-router-controlled', '');
return tag;
})
// Add the meta tags to the document head.
.forEach(tag => document.head.appendChild(tag));
next();
});
// ...
На данный момент у нас есть приложение Vue с маршрутами, метаполями и защитой навигации. Мы можем запустить следующую команду:
- npm run serve
И посетите localhost:8080
в веб-браузере. Теперь при изменении ваших маршрутов страница <title>
будет обновляться с указанием названия
ближайшего соответствующего маршрута. Точно так же обновятся и теги <meta>
.
Заключение
В этом руководстве вы узнали, как использовать метаполя и средства навигации для обновления заголовка страницы и метаданных при изменении маршрута.
Если вы используете предварительный рендеринг, то эти изменения будут встроены в ваши предварительно обработанные HTML-файлы и будут отлично работать для SEO. Для SSR (рендеринг на стороне сервера) это может быть немного сложнее.
Также стоит отметить, что при использовании этого метода не может быть и речи о динамических, часто обновляемых заголовках. Вам, вероятно, придется вручную обновлять document.title
для таких случаев использования.
Если вы хотите узнать больше о Vue.js, посетите нашу тематическую страницу Vue.js, где вы найдете упражнения и проекты по программированию.