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 中的标签
  • 标签去重:相同 nameproperty 的 meta 标签自动覆盖,不会重复
  • 树形合并:Layout → Page → Component,后者覆盖前者

1.2 三个 API 的关系

API能力适用场景
useHead()设置所有 head 标签(title、meta、link、script、style)通用场景
useSeoMeta()只设置 SEO 相关 meta(有类型提示)页面级 SEO
useServerSeoMeta()同上,但只在服务端执行纯 SEO meta(不需要客户端响应式)

三者的关系:useSeoMetauseHead 的语法糖,专门简化 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 提供了完整的类型提示,不需要手动写 nameproperty

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',
})

它会自动展开为对应的 &lt;meta&gt; 标签。比 useHead(&#123; meta: [...] &#125;) 写法简洁得多,还不容易拼错属性名。

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卡片类型:summarysummary_large_imageplayer
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 推荐的结构化数据格式,嵌入在 &lt;script type="application/ld+json"&gt; 中。

5.2 在 Nuxt4 中注入 JSON-LD

通过 useHeadscript 选项注入:

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 Cardcard + title + image
JSON-LDVideoObject + 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 指向默认语言版本,当用户语言不在支持列表中时使用。

本章小结

  • 三层 APIuseHead(通用)> 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 标签告知搜索引擎语言版本关系