SEO 和 Meta
通过强大的 head 配置、composables 和组件优化 Nuxt 应用的 SEO。
Nuxt 的 head 标签管理由 Unhead 驱动。它提供合理的默认值、多个强大的 composables 和丰富的配置选项来管理应用的 head 和 SEO meta 标签。
Nuxt 配置
在 nuxt.config.ts 中提供 app.head 属性,可以静态地自定义整个应用的 head。
这种方式不支持响应式数据。推荐在 app.vue 中使用 useHead()。
适合在这里设置不会变化的标签,比如站点标题默认值、语言和 favicon。
export default defineNuxtConfig({
app: {
head: {
title: 'Nuxt', // 默认兜底标题
htmlAttrs: {
lang: 'zh-CN',
},
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
],
},
},
})
当你设置了 app.cdnURL 后,public/ 目录下的资源(包括 favicon.ico)会从 CDN 提供。Nuxt 会根据 cdnURL 解析公共资源,回退到 app.baseURL。但是上面这种静态的 app.head link(如 href: '/favicon.ico')是字面路径,不会根据 cdnURL 解析。要让 favicon 指向解析后的位置,在 app.vue 中用 useHead() 从运行时配置构建 href:
<script setup lang="ts">
const { cdnURL, baseURL } = useRuntimeConfig().app
useHead({
link: [
{ rel: 'icon', type: 'image/x-icon', href: `${cdnURL || baseURL}favicon.ico` },
],
})
</script>
你也可以在类型部分列出的任意 key。
默认标签
Nuxt 默认提供了一些标签,确保网站开箱即用。
viewport:width=device-width, initial-scale=1charset:utf-8
大多数网站不需要覆盖这些默认值,但你可以使用快捷方式修改。
export default defineNuxtConfig({
app: {
head: {
// 更新 Nuxt 默认值
charset: 'utf-16',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
},
},
})
useHead
useHead composable 支持响应式输入,可以编程式地管理 head 标签。
<script setup lang="ts">
useHead({
title: '我的应用',
meta: [
{ name: 'description', content: '我的精彩网站。' },
],
bodyAttrs: {
class: 'test',
},
script: [{ innerHTML: 'console.log(\'Hello world\')' }],
})
</script>
推荐查看 useHead 和 useHeadSafe composable 文档。
useSeoMeta
useSeoMeta composable 让你以对象形式定义站点的 SEO meta 标签,完整类型安全。
这能帮你避免拼写错误和常见错误,比如用 name 代替 property。
<script setup lang="ts">
useSeoMeta({
title: '我的精彩网站',
ogTitle: '我的精彩网站',
description: '这是我的精彩网站,让我来告诉你关于它的一切。',
ogDescription: '这是我的精彩网站,让我来告诉你关于它的一切。',
ogImage: 'https://example.com/image.png',
twitterCard: 'summary_large_image',
})
</script>
组件
虽然推荐所有场景都使用 useHead,但你可能更喜欢在模板中用组件定义 head 标签。
Nuxt 提供了以下组件:<Title>、<Base>、<NoScript>、<Style>、<Meta>、<Link>、<Body>、<Html> 和 <Head>。注意这些组件都是大写的,确保不会和原生 HTML 标签冲突。
<Head> 和 <Body> 可以嵌套 meta 标签(为了美观),但这不影响嵌套标签在最终 HTML 中的渲染位置。
<script setup lang="ts">
const title = ref('Hello World')
</script>
<template>
<div>
<Head>
<Title>{{ title }}</Title>
<Meta
name="description"
:content="title"
/>
<Style>
body { background-color: green; }
</Style>
</Head>
<h1>{{ title }}</h1>
</div>
</template>
建议把组件包裹在 <Head> 或 <Html> 中,这样标签去重会更直观。
如果你需要在客户端-服务端边界重复标签,在 <Head> 组件上应用 key 属性。
类型
以下是 useHead、app.head 和组件使用的非响应式类型:
interface MetaObject {
title?: string
titleTemplate?: string | ((title?: string) => string)
templateParams?: Record<string, string | Record<string, string>>
base?: Base
link?: Link[]
meta?: Meta[]
style?: Style[]
script?: Script[]
noscript?: Noscript[]
htmlAttrs?: HtmlAttributes
bodyAttrs?: BodyAttributes
}
更详细的类型请参考 @unhead/vue。
特性
响应式
所有属性都支持响应式,可以传入 computed、getter 或响应式对象。
<script setup lang="ts">
const description = ref('我的精彩网站。')
useHead({
meta: [
{ name: 'description', content: description },
],
})
</script>
<script setup lang="ts">
const description = ref('我的精彩网站。')
useSeoMeta({
description,
})
</script>
<script setup lang="ts">
const description = ref('我的精彩网站。')
</script>
<template>
<div>
<Meta
name="description"
:content="description"
/>
</div>
</template>
标题模板
你可以使用 titleTemplate 选项来提供动态模板,自定义站点标题。比如给每个页面的标题加上站点名。
titleTemplate 可以是字符串(%s 会被替换为标题),也可以是函数。
如果你想用函数(完全控制),不能在 nuxt.config 中设置。推荐在 app.vue 中设置,这样会应用到站点的所有页面:
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} - 站点标题` : '站点标题'
},
})
</script>
现在,如果你在站点的另一个页面用 useHead 设置标题为 My Page,浏览器标签页会显示 'My Page - 站点标题'。你也可以传 null 来默认显示 '站点标题'。
模板参数
你可以使用 templateParams 在 titleTemplate 中提供除默认 %s 之外的占位符,实现更动态的标题生成。
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} %separator %siteName` : '%siteName'
},
templateParams: {
siteName: '站点标题',
separator: '-',
},
})
</script>
Body 标签
你可以在适用的标签上使用 tagPosition: 'bodyClose' 选项,将它们追加到 <body> 标签末尾。
例如:
<script setup lang="ts">
useHead({
script: [
{
src: 'https://third-party-script.com',
// 可选值:'head' | 'bodyClose' | 'bodyOpen'
tagPosition: 'bodyClose',
},
],
})
</script>
示例
配合 definePageMeta
在 app/pages/ 目录中,你可以使用 definePageMeta 配合 useHead 根据当前路由设置元数据。
比如,你可以先设置当前页面标题(这是在构建时通过宏提取的,所以不能动态设置):
<script setup lang="ts">
definePageMeta({
title: '某个页面',
})
</script>
然后在布局文件中,使用之前设置的路由元数据:
<script setup lang="ts">
const route = useRoute()
useHead({
meta: [{ property: 'og:title', content: `应用名称 - ${route.meta.title}` }],
})
</script>
动态标题
下面的例子中,titleTemplate 既可以是带 %s 占位符的字符串,也可以是 function,后者在为 Nuxt 应用的每个路由动态设置页面标题时提供更大的灵活性:
<script setup lang="ts">
useHead({
// 字符串形式,
// `%s` 会被替换为标题
titleTemplate: '%s - 站点标题',
})
</script>
<script setup lang="ts">
useHead({
// 或者函数形式
titleTemplate: (productCategory) => {
return productCategory
? `${productCategory} - 站点标题`
: '站点标题'
},
})
</script>
nuxt.config 也可以作为设置页面标题的替代方式。但是 nuxt.config 不支持动态页面标题。因此,推荐在 app.vue 文件中使用 titleTemplate 添加动态标题,这样会应用到 Nuxt 应用的所有路由。
外部 CSS
下面的例子展示了如何使用 useHead composable 的 link 属性或 <Link> 组件来启用 Google Fonts:
<script setup lang="ts">
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
crossorigin: '',
},
],
})
</script>
<template>
<div>
<Link
rel="preconnect"
href="https://fonts.googleapis.com"
/>
<Link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto&display=swap"
crossorigin=""
/>
</div>
</template>