useHead 与 SEO 元数据管理
搜索引擎和社交平台通过 HTML
<head>中的 meta 标签来理解你的页面内容。Nuxt4 基于 Unhead 库提供了三个层次的 SEO Composable——useHead(通用)、useSeoMeta(SEO 专用)、useServerSeoMeta(仅服务端)。理解它们的层级关系和覆盖机制,是做好 SEO 的基础。
1. Unhead 架构与三层 API
1.1 Unhead:Nuxt 的 Head 管理引擎
Nuxt4 的 <head> 管理由 Unhead 驱动(前身是 @vueuse/head)。它的核心设计是声明式 + 自动去重——你在任何组件中声明 meta 标签,Unhead 会自动合并、去重、排序,最终渲染出一份干净的 <head>。
关键特性:
- SSR 自动注入:服务端渲染时,Unhead 会将所有 meta 标签直接写入 HTML
- CSR 自动更新:客户端导航时,动态修改
document.head中的标签 - 标签去重:相同
name或property的 meta 标签自动覆盖,不会重复 - 树形合并:Layout → Page → Component,后者覆盖前者
1.2 三个 API 的关系
| API | 能力 | 适用场景 |
|---|---|---|
useHead() | 设置所有 head 标签(title、meta、link、script、style) | 通用场景 |
useSeoMeta() | 只设置 SEO 相关 meta(有类型提示) | 页面级 SEO |
useServerSeoMeta() | 同上,但只在服务端执行 | 纯 SEO meta(不需要客户端响应式) |
三者的关系:useSeoMeta 是 useHead 的语法糖,专门简化 meta 标签的书写;useServerSeoMeta 进一步优化——meta 标签只需要在 SSR 阶段渲染到 HTML 中(搜索引擎爬取的是 HTML),客户端不需要维护这些 Ref 的响应式开销。
原则:SEO meta 标签优先用 useServerSeoMeta,减少客户端 bundle 和内存开销。
1.3 覆盖优先级
当多个组件设置了同名 meta 时,Unhead 按后注册优先的规则处理:
app.vue(最先注册)
→ layouts/default.vue
→ pages/videos/[id].vue
→ components/VideoDetail.vue(最后注册,优先级最高)
也就是说,页面组件中设置的 title 会覆盖 Layout 中设置的 title。这符合直觉——越具体的组件,meta 越精准。
2. useHead 通用用法
useHead 可以设置 <head> 中的所有元素:
useHead({
title: 'AI 视频平台',
titleTemplate: '%s - AI Video Hub', // 模板,%s 替换为 title
meta: [
{ name: 'description', content: '用 AI 生成高质量视频' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', href: '/favicon.ico' },
{ rel: 'canonical', href: 'https://example.com/videos' },
],
script: [
{ src: 'https://analytics.example.com/script.js', defer: true },
],
htmlAttrs: { lang: 'zh-CN' },
bodyAttrs: { class: 'antialiased' },
})2.1 titleTemplate 的妙用
在 app.vue 或 Layout 中设置 titleTemplate,所有页面只需设置 title:
// app.vue
useHead({ titleTemplate: '%s - AI Video Hub' })
// pages/index.vue
useHead({ title: '首页' }) // → "首页 - AI Video Hub"
// pages/videos/[id].vue
useHead({ title: video.value?.title }) // → "猫咪视频 - AI Video Hub"如果某个页面想去掉后缀,可以把 titleTemplate 覆盖为 null。
2.2 响应式 Head
useHead 支持传入响应式值——当数据变化时,<head> 自动更新:
const video = ref<Video | null>(null)
useHead({
title: computed(() => video.value?.title || '加载中...'),
meta: [
{ name: 'description', content: computed(() => video.value?.description || '') },
],
})但要注意:如果 meta 只用于 SEO(搜索引擎只看 SSR 输出),响应式是浪费——此时应用 useServerSeoMeta。
3. useSeoMeta 与 useServerSeoMeta
3.1 类型安全的 SEO Meta
useSeoMeta 提供了完整的类型提示,不需要手动写 name 和 property:
useSeoMeta({
title: '猫咪AI视频合集',
description: '用 AI 生成的可爱猫咪视频,每日更新',
ogTitle: '猫咪AI视频合集',
ogDescription: '用 AI 生成的可爱猫咪视频',
ogImage: 'https://example.com/og/cats.jpg',
ogType: 'website',
twitterCard: 'summary_large_image',
twitterTitle: '猫咪AI视频合集',
twitterImage: 'https://example.com/og/cats.jpg',
})它会自动展开为对应的 <meta> 标签。比 useHead({ meta: [...] }) 写法简洁得多,还不容易拼错属性名。
3.2 useServerSeoMeta:性能最优选
// 只在服务端执行,客户端 bundle 中不包含这段代码
useServerSeoMeta({
title: video.value?.title,
description: video.value?.description,
ogImage: video.value?.thumbnail,
})什么时候该用哪个?
- 需要客户端动态更新 meta(如 SPA 导航切换页面 title)→
useSeoMeta - meta 只需要在 SSR HTML 中出现(搜索引擎爬虫看到即可)→
useServerSeoMeta - 大部分 SEO meta 属于后者,因为搜索引擎看的是初始 HTML,不执行 JavaScript
4. Open Graph 与 Twitter Card
4.1 Open Graph(社交分享卡片)
当用户在微信、Facebook、LinkedIn 等平台分享你的链接时,这些平台会读取 Open Graph meta 标签来生成预览卡片。
关键标签:
| 标签 | 必填 | 说明 |
|---|---|---|
og:title | ✅ | 分享标题 |
og:description | ✅ | 分享描述 |
og:image | ✅ | 预览图(推荐 1200×630px) |
og:url | ✅ | 页面规范 URL |
og:type | ✅ | 内容类型(website/article/video) |
og:site_name | - | 站点名称 |
og:locale | - | 语言(zh_CN) |
og:video | - | 视频 URL(视频平台必备) |
4.2 Twitter Card
Twitter 有自己的 meta 体系,如果未设置则回退到 Open Graph:
| 标签 | 说明 |
|---|---|
twitter:card | 卡片类型:summary、summary_large_image、player |
twitter:title | 标题(回退到 og:title) |
twitter:description | 描述 |
twitter:image | 预览图 |
twitter:player | 视频播放器 URL(video 类型专用) |
4.3 视频平台的 OG 最佳实践
视频平台应该使用 og:type=video.other 并提供视频元数据:
useServerSeoMeta({
ogType: 'video.other',
ogVideo: `https://example.com/embed/${videoId}`,
ogVideoType: 'text/html',
ogVideoWidth: '1280',
ogVideoHeight: '720',
ogImage: video.thumbnail,
ogImageWidth: '1280',
ogImageHeight: '720',
})5. JSON-LD 结构化数据
5.1 为什么需要 JSON-LD
Open Graph 让社交平台理解你的内容,JSON-LD 让搜索引擎(Google、Bing)深度理解并展示富摘要——包括评分星级、视频时长、面包屑导航等。
JSON-LD(JSON for Linking Data)是 Google 推荐的结构化数据格式,嵌入在 <script type="application/ld+json"> 中。
5.2 在 Nuxt4 中注入 JSON-LD
通过 useHead 的 script 选项注入:
useHead({
script: [{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'VideoObject',
name: video.value.title,
description: video.value.description,
thumbnailUrl: video.value.thumbnail,
uploadDate: video.value.createdAt,
duration: formatISO8601Duration(video.value.duration),
contentUrl: video.value.url,
embedUrl: `https://example.com/embed/${video.value.id}`,
interactionStatistic: {
'@type': 'InteractionCounter',
interactionType: 'https://schema.org/WatchAction',
userInteractionCount: video.value.views,
},
}),
}],
})5.3 常用 Schema 类型
| Schema 类型 | 搜索结果展示 | 适用页面 |
|---|---|---|
VideoObject | 视频缩略图、时长、上传日期 | 视频详情页 |
Article | 发布日期、作者、面包屑 | 博客/教程页 |
BreadcrumbList | 面包屑导航 | 所有页面 |
Organization | 公司 Logo、社交链接 | 首页 |
WebSite + SearchAction | 站内搜索框 | 首页 |
FAQPage | 常见问题折叠展示 | FAQ 页 |
5.4 封装为通用 Composable
// app/composables/useJsonLd.ts
export function useJsonLd(data: Record<string, any>) {
useHead({
script: [{
type: 'application/ld+json',
innerHTML: JSON.stringify({ '@context': 'https://schema.org', ...data }),
}],
})
}
// 使用
useJsonLd({
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: '首页', item: 'https://example.com' },
{ '@type': 'ListItem', position: 2, name: 'AI 视频', item: 'https://example.com/videos' },
{ '@type': 'ListItem', position: 3, name: video.value.title },
],
})6. AI 视频详情页 SEO 完整方案
将前面所有知识串联,一个视频详情页的完整 SEO 实现:
// pages/videos/[id].vue — <script setup>
const route = useRoute()
const { data: video } = await useFetch(`/api/videos/${route.params.id}`)
if (!video.value) showError({ statusCode: 404, statusMessage: '视频不存在' })
// 1. 基础 SEO Meta(仅服务端渲染,不占客户端内存)
useServerSeoMeta({
title: video.value.title,
description: video.value.description?.slice(0, 160),
ogTitle: video.value.title,
ogDescription: video.value.description?.slice(0, 160),
ogImage: video.value.thumbnail,
ogType: 'video.other',
ogVideo: `https://example.com/embed/${video.value.id}`,
twitterCard: 'player',
twitterTitle: video.value.title,
twitterImage: video.value.thumbnail,
})
// 2. 页面 Title(需要客户端响应式——SPA 导航时更新标签页标题)
useHead({ title: video.value.title })
// 3. Canonical URL(避免重复内容)
useHead({
link: [{ rel: 'canonical', href: `https://example.com/videos/${video.value.id}` }],
})
// 4. JSON-LD 结构化数据(视频 + 面包屑)
useJsonLd({
'@type': 'VideoObject',
name: video.value.title,
description: video.value.description,
thumbnailUrl: video.value.thumbnail,
uploadDate: video.value.createdAt,
duration: `PT${Math.floor(video.value.duration / 60)}M${video.value.duration % 60}S`,
contentUrl: video.value.url,
})6.1 SEO Checklist
| 项目 | 检查点 | 状态 |
|---|---|---|
| Title | 每页唯一,包含核心关键词,< 60 字符 | ✅ |
| Description | 每页唯一,包含行动号召,< 160 字符 | ✅ |
| Canonical | 避免 URL 参数导致的重复内容 | ✅ |
| OG Tags | 标题 + 描述 + 图片 + URL + 类型 | ✅ |
| Twitter Card | card + title + image | ✅ |
| JSON-LD | VideoObject + BreadcrumbList | ✅ |
| SSR | 所有 meta 在 HTML 源码中可见 | ✅ |
| 图片 | OG Image 1200×630px,< 5MB | ✅ |
7. 多语言 SEO
如果站点支持多语言,需要设置 hreflang 标签告诉搜索引擎不同语言版本的关系:
useHead({
link: [
{ rel: 'alternate', hreflang: 'zh-CN', href: 'https://example.com/zh/videos/123' },
{ rel: 'alternate', hreflang: 'en', href: 'https://example.com/en/videos/123' },
{ rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/videos/123' },
],
htmlAttrs: { lang: currentLocale.value },
})x-default 指向默认语言版本,当用户语言不在支持列表中时使用。
本章小结
- 三层 API:
useHead(通用)>useSeoMeta(SEO 语法糖)>useServerSeoMeta(性能最优) - 覆盖机制:后注册的组件覆盖先注册的,Page 覆盖 Layout,Component 覆盖 Page
- titleTemplate:在 Layout 设置模板,页面只需提供标题
- Open Graph:社交分享卡片的基础,视频平台用
og:type=video.other - JSON-LD:Google 富摘要的关键,视频页必备
VideoObject,全站加BreadcrumbList - 性能原则:SEO meta 用
useServerSeoMeta(不占客户端内存),只有需要 SPA 导航更新的 title 用useHead - 多语言:
hreflang标签告知搜索引擎语言版本关系