You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt应用动态meta标签页面源码显示undefined解决方案

Nuxt动态路由页面静态源码meta值为undefined的修复方案

问题现象

  • 博客详情动态路由页面,客户端渲染完成后metadataObject可正常拿到接口返回值,浏览器元素检查面板中meta标签显示正常
  • 点击浏览器「查看页面源码」时,所有meta标签属性值均为undefined,无法满足SEO爬虫抓取要求

问题根因

之前的推测只对了一半,核心问题有两个:

  1. 显式配置了fetchOnServer: false,直接导致服务端渲染阶段完全不执行fetch钩子,服务端生成静态HTML时metadataObject还是初始空对象,自然读不到任何属性,返回给爬虫的静态源码里meta全是undefined
  2. 即使开启服务端fetch,当前代码里的逻辑也存在时序问题:直接操作DOM(document.getElementById('blog-html').innerHTML)的写法在Nuxt服务端渲染阶段会直接报错,因为服务端没有document对象;同时数据更新后没有主动触发meta信息刷新,会导致客户端路由切换时meta不同步

修复步骤

1. 开启服务端数据拉取

删除配置项fetchOnServer: false,让fetch钩子在服务端渲染阶段就执行,提前拿到博客数据填充到响应式数据中,保证head钩子生成时能读到对应的值。

2. 修正fetch逻辑,避免服务端报错

  • 移除直接操作DOM的代码,富文本渲染结果不要用原生DOM插入,改成存在响应式变量里,在模板中用v-html渲染
  • 给metadataObject设置默认初始值,避免数据未返回时head读取属性报错
  • 接口请求完成后,客户端环境下主动调用this.$meta().refresh()触发meta更新,保证路由切换时meta同步

3. 修正head配置和笔误

  • 给所有meta字段加默认兜底值,避免数据未加载完成时生成空meta
  • 修正canonical链接的属性笔误:原代码写的是herf,正确属性名是href
  • 修正重复hid冲突:原代码og:title的hid设置为title,和页面默认title的hid重复,会导致标签覆盖,改成唯一值即可

修复后核心代码示例

<template>
  <div>
    <QcLoader v-if="$fetchState.pending" />
    <heroSection :blog="blog" />
    <remoteSection :blog="blog" />
    <!-- 富文本内容用v-html渲染,禁止原生操作DOM -->
    <div id="blog-html" v-html="renderedBlogHtml"></div>
    <readmoreSection :more-blogs="moreBlogs" />
  </div>
</template>
<script>
import { BLOCKS } from '@contentful/rich-text-types'
import { documentToHtmlString } from '@contentful/rich-text-html-renderer'
import client from '@/plugins/contentful'
export default {
  name: 'Blogid',
  components: {
    QcLoader: () => import('@/components/partials/QcLoader.vue'),
    heroSection: () => import('@/components/sections/company/blog/post/heroSection.vue'),
    remoteSection: () => import('@/components/sections/company/blog/post/remoteSection.vue'),
    readmoreSection: () => import('@/components/sections/company/blog/post/readmoreSection.vue'),
  },
  data() {
    return {
      blog: [],
      moreBlogs: [],
      renderedBlogHtml: '',
      // 给meta对象加默认兜底值
      metadataObject: {
        title: 'QED博客',
        description: 'QED技术博客内容',
        url: 'https://qedcode.io/company/blog',
        imageUrl: '',
        imageWidth: '',
        imageHeight: ''
      }
    }
  },
  async fetch() {
    // 并行拉取博客详情和推荐博客内容,缩短加载时间
    const [blogRes, moreBlogRes] = await Promise.all([
      client.getEntries({
        content_type: 'blogPost',
        'fields.slug[in]': this.$route.params.blog,
      }),
      client.getEntries({
        content_type: 'blogPost',
        order: '-sys.createdAt',
      })
    ]).catch(err => {
      this.moreBlogs = []
      return [null, {items: []}]
    })

    if (blogRes?.items?.length) {
      const blog = blogRes.items[0]
      this.blog = blog
      // 赋值meta数据
      this.metadataObject = {
        title: blog.fields.title,
        description: blog.fields.firstTestField.content[0].content[0].value || blog.fields.firstTestField.content[0].content[0].content[0].value,
        url: `https://qedcode.io/company/blog/${blog.fields.slug}`,
        imageUrl: blog.fields.image.fields.file.url,
        imageWidth: blog.fields.image.fields.file.details.image.width,
        imageHeight: blog.fields.image.fields.file.details.image.height,
      }
      // 服务端渲染富文本
      const rawRichTextField = blog.fields.firstTestField
      const options = {
        renderNode: {
          [BLOCKS.EMBEDDED_ASSET]: (node) => {
            const file = node.data.target.fields.file
            if (['image/jpeg', 'image/png'].includes(file.contentType)) {
              return `<img src="${file.url}">`
            }
          },
        },
      }
      this.renderedBlogHtml = documentToHtmlString(rawRichTextField, options)
      // 客户端环境下主动刷新meta
      if (process.client) {
        this.$meta().refresh()
      }
    }
    this.moreBlogs = moreBlogRes.items
  },
  head() {
    return {
      title: this.metadataObject.title,
      titleTemplate: '%s',
      meta: [
        {
          name: 'description',
          hid: 'description',
          content: this.metadataObject.description,
        },
        /* Open Graph */
        {
          name: 'og:title',
          hid: 'og:title',
          content: this.metadataObject.title,
        },
        {
          name: 'og:site_name',
          hid: 'site-name',
          content: 'QED',
        },
        {
          name: 'og:url',
          hid: 'url',
          content: this.metadataObject.url,
        },
        {
          name: 'og:description',
          hid: 'og:description',
          content: this.metadataObject.description,
        },
        {
          name: 'og:type',
          hid: 'type',
          content: 'article',
        },
        {
          name: 'og:image',
          hid: 'image',
          content: this.metadataObject.imageUrl,
        },
        {
          name: 'og:image:width',
          hid: 'image-width',
          content: this.metadataObject.imageWidth,
        },
        {
          name: 'og:image:height',
          hid: 'image-height',
          content: this.metadataObject.imageHeight,
        },
        /* Twitter */
        { name: 'twitter:card', content: 'summary_large_image' },
        { name: 'twitter:site', content: '@QED_code' },
        {
          name: 'twitter:title',
          content: this.metadataObject.title,
        },
        {
          name: 'twitter:description',
          content: this.metadataObject.description,
        },
        { name: 'twitter:url', content: this.metadataObject.url },
        {
          name: 'twitter:image',
          content: this.metadataObject.imageUrl,
        },
      ],
      link: [
        {
          rel: 'canonical',
          href: this.metadataObject.url,
        },
      ],
    }
  }
}
</script>

额外优化点

  • 博客详情页的og:type建议从website改成article,更符合内容页语义,社交平台抓取时展示效果更好
  • 移除无意义的fetchDelay: 1000配置,该配置会强制延迟1秒发起请求,拖慢服务端渲染速度,无特殊需求不需要添加
  • 接口请求用Promise.all并行拉取,相比原来的串行请求可减少近一半的加载等待时间

内容的提问来源于stack exchange,提问作者76marin76

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 14:24:26