Nuxt.js元数据与SEO #

一、SEO概述 #

搜索引擎优化(SEO)对于网站的可见性至关重要。Nuxt.js 通过服务端渲染(SSR)和完善的元数据管理,为 SEO 提供了强大的支持。

1.1 Nuxt.js的SEO优势 #

  • 服务端渲染:搜索引擎可以直接抓取完整内容
  • 元数据管理:灵活的 useHead 组合式函数
  • 站点地图:自动生成 sitemap
  • 结构化数据:支持 JSON-LD

二、useHead基础 #

2.1 基本用法 #

vue
<script setup lang="ts">
useHead({
  title: '页面标题',
  meta: [
    { name: 'description', content: '页面描述' }
  ]
})
</script>

2.2 支持的属性 #

vue
<script setup lang="ts">
useHead({
  title: '页面标题',
  titleTemplate: (title) => `${title} - 我的网站`,
  meta: [
    { name: 'description', content: '页面描述' },
    { name: 'keywords', content: '关键词1, 关键词2' },
    { name: 'author', content: '作者名' },
    { name: 'robots', content: 'index, follow' }
  ],
  link: [
    { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
    { rel: 'canonical', href: 'https://example.com/page' }
  ],
  script: [
    { src: '/script.js', defer: true }
  ],
  style: [
    { children: 'body { margin: 0; }' }
  ],
  htmlAttrs: {
    lang: 'zh-CN'
  },
  bodyAttrs: {
    class: 'dark-mode'
  }
})
</script>

三、页面标题 #

3.1 静态标题 #

vue
<script setup lang="ts">
useHead({
  title: '关于我们'
})
</script>

3.2 动态标题 #

vue
<script setup lang="ts">
const { data: article } = await useFetch('/api/article/1')

useHead({
  title: computed(() => article.value?.title || '加载中...')
})
</script>

3.3 标题模板 #

nuxt.config.ts 中设置全局模板:

typescript
export default defineNuxtConfig({
  app: {
    head: {
      titleTemplate: '%s - 我的网站'
    }
  }
})

页面中使用:

vue
<script setup lang="ts">
useHead({
  title: '首页'
})
</script>

结果:首页 - 我的网站

3.4 自定义模板 #

vue
<script setup lang="ts">
useHead({
  title: '文章详情',
  titleTemplate: (title) => `${title} | 博客 | 我的网站`
})
</script>

四、Meta标签 #

4.1 基本Meta #

vue
<script setup lang="ts">
useHead({
  meta: [
    { name: 'description', content: '这是页面描述,用于搜索引擎展示' },
    { name: 'keywords', content: 'Nuxt.js, Vue.js, SSR' },
    { name: 'author', content: '作者名' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' },
    { name: 'robots', content: 'index, follow' }
  ]
})
</script>

4.2 Open Graph #

vue
<script setup lang="ts">
useHead({
  meta: [
    { property: 'og:title', content: '分享标题' },
    { property: 'og:description', content: '分享描述' },
    { property: 'og:image', content: 'https://example.com/image.png' },
    { property: 'og:url', content: 'https://example.com/page' },
    { property: 'og:type', content: 'article' },
    { property: 'og:site_name', content: '我的网站' }
  ]
})
</script>

4.3 Twitter Card #

vue
<script setup lang="ts">
useHead({
  meta: [
    { name: 'twitter:card', content: 'summary_large_image' },
    { name: 'twitter:title', content: '分享标题' },
    { name: 'twitter:description', content: '分享描述' },
    { name: 'twitter:image', content: 'https://example.com/image.png' },
    { name: 'twitter:site', content: '@twitter_handle' }
  ]
})
</script>

4.4 动态Meta #

vue
<script setup lang="ts">
const { data: article } = await useFetch('/api/article/1')

useHead({
  meta: [
    {
      name: 'description',
      content: computed(() => article.value?.excerpt || '')
    },
    {
      property: 'og:title',
      content: computed(() => article.value?.title || '')
    },
    {
      property: 'og:image',
      content: computed(() => article.value?.coverImage || '/default-og.png')
    }
  ]
})
</script>

五、Link标签 #

vue
<script setup lang="ts">
useHead({
  link: [
    { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
    { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' },
    { rel: 'canonical', href: 'https://example.com/page' },
    { rel: 'alternate', type: 'application/rss+xml', href: '/rss.xml' }
  ]
})
</script>

5.2 CSS样式表 #

vue
<script setup lang="ts">
useHead({
  link: [
    { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Roboto' }
  ]
})
</script>

5.3 预加载和预连接 #

vue
<script setup lang="ts">
useHead({
  link: [
    { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
    { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: true },
    { rel: 'preload', href: '/fonts/custom.woff2', as: 'font', type: 'font/woff2', crossorigin: true }
  ]
})
</script>

六、全局配置 #

6.1 nuxt.config.ts #

typescript
export default defineNuxtConfig({
  app: {
    head: {
      title: '我的网站',
      titleTemplate: '%s - 我的网站',
      meta: [
        { name: 'description', content: '网站描述' },
        { name: 'keywords', content: '关键词1, 关键词2' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
        { charset: 'utf-8' }
      ],
      link: [
        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
      ],
      htmlAttrs: {
        lang: 'zh-CN'
      }
    }
  }
})

6.2 app.config.ts #

typescript
export default defineAppConfig({
  title: '我的网站',
  description: '网站描述',
  ogImage: '/og-image.png'
})

使用:

vue
<script setup lang="ts">
const appConfig = useAppConfig()

useHead({
  meta: [
    { property: 'og:image', content: appConfig.ogImage }
  ]
})
</script>

七、结构化数据 #

7.1 JSON-LD #

vue
<script setup lang="ts">
const { data: article } = await useFetch('/api/article/1')

useHead({
  script: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: article.value?.title,
        description: article.value?.excerpt,
        author: {
          '@type': 'Person',
          name: article.value?.author
        },
        datePublished: article.value?.publishedAt,
        dateModified: article.value?.updatedAt
      })
    }
  ]
})
</script>

7.2 产品结构化数据 #

vue
<script setup lang="ts">
const { data: product } = await useFetch('/api/product/1')

useHead({
  script: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Product',
        name: product.value?.name,
        description: product.value?.description,
        image: product.value?.images,
        offers: {
          '@type': 'Offer',
          price: product.value?.price,
          priceCurrency: 'CNY',
          availability: 'https://schema.org/InStock'
        }
      })
    }
  ]
})
</script>

八、站点地图 #

8.1 安装模块 #

bash
pnpm add @nuxtjs/sitemap

8.2 配置 #

typescript
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],
  
  site: {
    url: 'https://example.com'
  },
  
  sitemap: {
    sources: ['/api/sitemap']
  }
})

8.3 动态站点地图 #

server/api/sitemap.ts

typescript
export default defineEventHandler(async () => {
  const posts = await $fetch('/api/posts')
  
  return posts.map(post => ({
    loc: `/blog/${post.slug}`,
    lastmod: post.updatedAt,
    changefreq: 'weekly',
    priority: 0.8
  }))
})

九、robots.txt #

9.1 配置 #

typescript
export default defineNuxtConfig({
  robots: {
    UserAgent: '*',
    Allow: '/',
    Disallow: ['/admin', '/private'],
    Sitemap: 'https://example.com/sitemap.xml'
  }
})

9.2 动态robots.txt #

server/routes/robots.txt.ts

typescript
export default defineEventHandler(() => {
  return `User-agent: *
Allow: /
Disallow: /admin
Disallow: /private
Sitemap: https://example.com/sitemap.xml`
})

十、SEO最佳实践 #

10.1 完整示例 #

pages/blog/[slug].vue

vue
<script setup lang="ts">
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)

if (!article.value) {
  throw createError({ statusCode: 404, message: '文章不存在' })
}

const canonicalUrl = computed(() => 
  `https://example.com/blog/${route.params.slug}`
)

useHead({
  title: article.value.title,
  meta: [
    { name: 'description', content: article.value.excerpt },
    { name: 'keywords', content: article.value.tags?.join(', ') },
    { name: 'author', content: article.value.author },
    { name: 'robots', content: 'index, follow' },
    { property: 'og:title', content: article.value.title },
    { property: 'og:description', content: article.value.excerpt },
    { property: 'og:image', content: article.value.coverImage },
    { property: 'og:url', content: canonicalUrl },
    { property: 'og:type', content: 'article' },
    { property: 'article:published_time', content: article.value.publishedAt },
    { property: 'article:author', content: article.value.author },
    { name: 'twitter:card', content: 'summary_large_image' },
    { name: 'twitter:title', content: article.value.title },
    { name: 'twitter:description', content: article.value.excerpt },
    { name: 'twitter:image', content: article.value.coverImage }
  ],
  link: [
    { rel: 'canonical', href: canonicalUrl }
  ],
  script: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: article.value.title,
        description: article.value.excerpt,
        image: article.value.coverImage,
        author: {
          '@type': 'Person',
          name: article.value.author
        },
        datePublished: article.value.publishedAt,
        dateModified: article.value.updatedAt,
        mainEntityOfPage: {
          '@type': 'WebPage',
          '@id': canonicalUrl.value
        }
      })
    }
  ]
})
</script>

<template>
  <article>
    <header>
      <h1>{{ article.title }}</h1>
      <p>{{ article.publishedAt }} · {{ article.author }}</p>
    </header>
    <div v-html="article.content" />
  </article>
</template>

10.2 SEO检查清单 #

  • [ ] 每个页面有唯一的标题
  • [ ] 每个页面有描述性 meta description
  • [ ] 使用语义化 HTML 标签
  • [ ] 图片有 alt 属性
  • [ ] 设置 canonical URL
  • [ ] 添加 Open Graph 标签
  • [ ] 添加 Twitter Card 标签
  • [ ] 生成站点地图
  • [ ] 配置 robots.txt
  • [ ] 添加结构化数据

十一、总结 #

本章介绍了 Nuxt.js 元数据与 SEO:

  • 使用 useHead 管理页面元数据
  • 配置页面标题和模板
  • 添加 Open Graph 和 Twitter Card
  • 使用结构化数据增强搜索展示
  • 生成站点地图和配置 robots.txt
  • SEO 最佳实践

良好的 SEO 可以显著提升网站的搜索引擎排名和流量,下一章我们将学习数据获取。

最后更新:2026-03-28