介绍
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
。并为嵌套添加路由"Frequently Asked Questions"
:
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
接下来,添加<router-view>
这样嵌套路线显示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
. 并添加<router-link>
了"Frequently Asked Questions"
:
<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公司的应用程序"Home"
,"About"
和"Frequently Asked Questions"
。我们可以运行以下命令:
- npm run serve
并localhost:8080
在网络浏览器中访问。单击导航链接应显示预期的组件。但是,<title>
和<meta>
标签还没有改变。
第 3 步 – 添加路由元字段和导航守卫
vue-router
支持路由元字段的title
和meta
值。让我们重新审视我们的路线并添加元字段。
打开router/index.js
:
- nano src/router/index.js
并添加meta
字段"Home"
,"About"
以及"Frequently Asked Questions"
:
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>
将更新为最近匹配的路线的title
. 同样,<meta>
标签也会更新。
结论
在本教程中,您学习了如何使用元字段和导航守卫在路由更改时更新页面标题和元数据。
如果您使用prerendering,那么这些更改将被烘焙到您的预渲染 HTML 文件中,并且非常适合 SEO。对于 SSR(服务器端渲染),它可能会更复杂一些。
还值得注意的是,使用这种方法无法获得动态的、经常更新的标题。document.title
对于此类用例,您可能不得不坚持手动更新。
如果您想了解有关 Vue.js 的更多信息,请查看我们的 Vue.js 主题页面以获取练习和编程项目。