---
title: "How to Debug Vue SEO Issues"
description: "Fix Unhead setup mistakes, hydration mismatches, and meta tags that won't render in Vue 3, verified with View Source and Search Console."
canonical_url: "https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging"
last_updated: "2026-07-16"
---

<key-takeaways>

- View Source shows what Google sees first. Inspect Element shows post-JavaScript DOM
- Hydration mismatches break meta tags and cause layout shifts
- URL Inspection in Search Console shows exactly what Google renders

</key-takeaways>

If you can't see meta tags in View Source, neither can Google. Most Vue SEO bugs come from client-side rendering, hydration mismatches, or Unhead configuration mistakes.

## View Source vs Inspect Element

Two different ways to view HTML show different content:

**View Source** (Right-click → View Page Source) shows the initial HTML your server sends. This is what Google's crawler sees first.

**Inspect Element** (F12 → Elements tab) shows the live DOM after JavaScript executes. Meta tags added by JavaScript appear here but might not be indexed.

Test: Open your site, right-click, select "View Page Source." Search for `<title>` or `<meta name="description"`. If you can't find your content in View Source, Google can't either.

```html
<!-- ❌ BAD: Empty in View Source (SPA without SSR) -->
<!DOCTYPE html>
<html>
  <head>
    <title>Loading...</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/app.js"></script>
  </body>
</html>

<!-- ✅ GOOD: Content in View Source (SSR) -->
<!DOCTYPE html>
<html>
  <head>
    <title>How to Debug Vue SEO | MySite</title>
    <meta name="description" content="Fix meta tags not rendering in Vue apps...">
  </head>
  <body>
    <div id="app">
      <h1>How to Debug Vue SEO</h1>
      <p>Fix meta tags not rendering...</p>
    </div>
    <script src="/app.js"></script>
  </body>
</html>
```

Google [processes pages in three phases](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics): crawling, rendering, and indexing. A page that needs JavaScript to show its content has to wait for the rendering phase before Google can index it, and Google publishes no fixed timeline for that wait. If critical content only appears after JavaScript executes, indexing gets delayed.

## Meta Tags Not Updating on Navigation

Single Page Applications change routes without full page reloads. Meta tags set during initial render might not update when users navigate.

**Common mistake:** Using `document.title` instead of Unhead.

```vue
<!-- ❌ Doesn't work on navigation -->
<script setup lang="ts">
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

watch(() => route.path, () => {
  document.title = route.meta.title // Won't update in head manager
})
</script>
```

Fix: Use `useHead()` from Unhead. It tracks navigation and updates meta tags correctly.

```vue
<!-- ✅ Updates on every navigation -->
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

const route = useRoute()

useHead({
  title: () => route.meta.title as string
})
</script>
```

Unhead synchronizes meta tags across SSR and client-side navigation. The [Unhead documentation](https://unhead.unjs.io/docs/head/api/composables/use-head) explains reactivity patterns.

Open Chrome DevTools while navigating between pages, and watch the `<head>` section in Elements tab:

1. Open DevTools (F12)
2. Select Elements tab
3. Find `<head>` element
4. Navigate to different page
5. Watch for meta tag updates

If meta tags don't change, you're not using `useHead()` or reactive values correctly.

## Hydration Mismatches Breaking Meta Tags

Hydration is when Vue takes server-rendered HTML and "activates" it with reactivity and event listeners. A hydration mismatch occurs when the HTML rendered on the client differs from the server-rendered HTML.

Vue displays warnings like "Hydration completed but contains mismatches" or "Text content does not match server-rendered HTML." These warnings appear in browser console and indicate server/client HTML differences ([Vue SSR Guide](https://vuejs.org/guide/scaling-up/ssr.html)).

### Common Causes

**Invalid HTML nesting** triggers browser auto-correction, causing mismatches:

```vue
<!-- ❌ Invalid: div inside p -->
<template>
  <p /><div>Content</div> <!-- Browser auto-closes <p> before <div> -->
</template>

<!-- ✅ Valid HTML structure -->
<template>
  <div>
    <p>Content</p>
  </div>
</template>
```

**Browser-only APIs** don't exist during SSR:

```vue
<!-- ❌ Crashes during SSR -->
<script setup lang="ts">
const userAgent = window.navigator.userAgent
</script>

<!-- ✅ Only access on client -->
<script setup lang="ts">
import { onMounted, ref } from 'vue'

const userAgent = ref('')

onMounted(() => {
  userAgent.value = window.navigator.userAgent
})
</script>
```

**Values computed during rendering** can differ between server and client:

```vue
<!-- ❌ Different on server vs client -->
<script setup lang="ts">
const timestamp = new Date().toLocaleString()
</script>

<!-- ✅ Use v-if + onMounted -->
<script setup lang="ts">
import { onMounted, ref } from 'vue'

const randomId = ref('')
const mounted = ref(false)

onMounted(() => {
  mounted.value = true
  randomId.value = Math.random().toString()
})
</script>

<template>
  <div v-if="mounted">
    ID: {{ randomId }}
  </div>
</template>
```

**Third-party libraries** without SSR support cause mismatches ([How to Fix Vue Hydration Mismatch](https://dev.to/jacobandrewsky/how-to-fix-vue-hydration-mismatch-47eg)):

```vue
<!-- Wrap client-only libraries -->
<template>
  <ClientOnly>
    <GoogleMap />
  </ClientOnly>
</template>
```

### Suppressing Mismatches in Vue 3.5+

For inevitable mismatches (like timestamps), use `data-allow-mismatch`:

```vue
<template>
  <time :data-allow-mismatch="true">
    {{ new Date().toLocaleString() }}
  </time>
</template>
```

This tells Vue to expect differences and skip warnings.

Two external factors also cause mismatches. **HTML minification** rewrites whitespace between server and client renders. Disable most HTML minifiers ([Harlan Wilton - Nuxt 3 Hydration Mismatch](https://harlanzw.com/blog/nuxt3-hydration-node-mismatch)).

[Cloudflare](https://cloudflare.com) users: Disable Cloudflare's automatic HTML minifier in dashboard → Speed → Optimization → Auto Minify.

**Browser cache** loads outdated JavaScript. Clear cache when debugging mismatches.

## Unhead Configuration Mistakes

Most Unhead bugs come from setup errors or missing SSR support.

### Missing createHead() Setup

Unhead requires initialization. Without it, `useHead()` silently fails.

```ts
// ❌ Missing Unhead setup
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')
```

Fix: Call `createHead()` and install plugin ([Unhead Installation Guide](https://unhead.unjs.io/docs/vue/head/guides/get-started/installation)):

```ts
// ✅ Correct Unhead setup
import { createHead } from '@unhead/vue/client'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
const head = createHead()
app.use(head)
app.mount('#app')
```

For SSR apps, import from `@unhead/vue/server` instead.

### Wrong Import Paths (Unhead v2)

Unhead v2 changed import paths. Old imports fail silently or throw errors.

```ts
// ❌ Old Unhead v1 imports
import { createHead } from '@unhead/vue'

// ✅ Unhead v2 subpath imports
import { createHead } from '@unhead/vue/client' // Client-side
import { createHead } from '@unhead/vue/server' // Server-side
```

Check [Unhead v2 migration guide](https://unhead.unjs.io/docs/vue/head/guides/get-started/migration) for breaking changes.

### Not Using SSR

Client-only Unhead won't help SEO. Meta tags appear in DevTools but not View Source.

```ts
// ❌ Client-only setup (no SEO benefit)
import { createHead } from '@unhead/vue/client'

// ✅ SSR setup (meta tags in initial HTML)
import { createHead } from '@unhead/vue/server'
```

Without SSR, search engines see empty `<div id="app"></div>` containers.

### Reactive Values Not Updating

Passing `.value` instead of the ref breaks reactivity:

```vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { ref } from 'vue'

const pageTitle = ref('Loading...')

// ❌ Loses reactivity
useHead({
  title: pageTitle.value
})

// ✅ Stays reactive
useHead({
  title: pageTitle
})

// Later update works with reactive version
pageTitle.value = 'Loaded Content'
</script>
```

For computed values, use getter functions:

```vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { computed, ref } from 'vue'

const post = ref({ title: 'Loading...' })

useHead({
  title: () => post.value.title // Updates when post changes
})
</script>
```

### Duplicate Meta Tags

Multiple `useHead()` calls with same meta tags create duplicates:

```vue
<!-- ❌ Creates 2 description tags -->
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  meta: [{ name: 'description', content: 'First description' }]
})

useHead({
  meta: [{ name: 'description', content: 'Second description' }]
})
</script>
```

Unhead deduplicates by default but multiple calls can override unexpectedly. Consolidate meta tags into a single `useHead()` call per component.

## Chrome DevTools SEO Workflow

DevTools helps debug meta tags, rendering issues, and JavaScript errors.

### 1. Check Initial HTML Response

Network panel shows server-sent HTML before JavaScript executes:

1. Open DevTools (F12)
2. Network tab → Clear (trash icon)
3. Reload page
4. Click first request (usually document)
5. Preview or Response tab shows initial HTML

Look for meta tags in this response. If missing, your SSR isn't working.

To find all meta tags at once, switch to the Elements panel, press Ctrl+F (Cmd+F on Mac), and search for `<meta name="description"`. Compare what you find against your `useHead()` calls.

### 2. Console Errors

JavaScript errors break meta tag updates. Console shows Vue warnings and Unhead errors.

Look for:

- "Hydration completed but contains mismatches"
- "Unhead is missing Vue context"
- Failed fetch requests preventing data load
- Component errors during SSR

Fix errors before debugging meta tags. Broken JavaScript breaks everything.

### 3. Lighthouse SEO Audit

Lighthouse tab includes basic SEO checks:

1. Lighthouse tab
2. Select "SEO" category
3. Click "Generate report"

Checks:

- Document has `<title>` element
- Document has meta description
- Links have descriptive text
- `<html>` has `lang` attribute

Lighthouse checks presence, not correctness: a title of "undefined" passes the audit but isn't useful to anyone.

## Google Search Console URL Inspection

URL Inspection tool shows exactly what Google sees when crawling your page.

### How to Use URL Inspection

1. Open [Google Search Console](https://search.google.com/search-console)
2. Enter URL in search bar at top
3. Click "Test Live URL" for current version
4. View results

Tool shows ([Google URL Inspection documentation](https://support.google.com/webmasters/answer/9012289)):

- Whether page is indexed
- How it was crawled
- What resources were blocked
- Structured data Google found
- Rendered page screenshot

"Whether page is indexed" resolves to one of two states: "URL is on Google" means the URL is eligible to appear in search results (not guaranteed), while "URL is not on Google" means the URL can't appear. Check "Page indexing" section for why.

### View Crawled Page

See how Google renders your page:

1. Click "View tested page"
2. Screenshot tab shows visual render
3. More info → HTML shows rendered HTML
4. More info → Console log shows JavaScript errors

Compare screenshot to your actual page. Missing content indicates rendering problems.

### JavaScript Rendering Test

Google [processes pages in three phases](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics): crawling, rendering, then indexing. Content that only shows up once JavaScript has rendered waits for that second phase before it can be indexed. URL Inspection shows both:

- "Live test" → Current rendered version
- "Indexed version" → What's in Google's index

### Request Indexing

After fixing issues:

1. URL Inspection → "Request Indexing"
2. Google queues the URL for recrawl

[Google doesn't guarantee indexing](https://support.google.com/webmasters/answer/7440203) or a specific timeline. It still evaluates content quality first.

The UI has a daily quota per property. The [Indexing API](https://developers.google.com/search/apis/indexing-api/v3/quickstart) is faster for bulk operations, but it only covers pages marked up with `JobPosting` or `BroadcastEvent` (livestream) structured data, not general pages, and defaults to a 200-request daily quota.

## Common Vue SEO Debugging Scenarios

### Meta Tags Appear in DevTools But Not View Source

**Cause:** Client-side rendering without SSR.

**Test:**

```bash
curl -s https://yoursite.com | grep "<title>"
```

If curl shows empty title, you're not using SSR.

**Fix:** Implement SSR using Vite SSR or a framework like Nuxt. See [Vue rendering modes](/learn-seo/vue/routes-and-rendering/rendering) guide.

### Meta Tags Don't Update on Navigation

**Cause:** Not using Unhead, or using `document.title` directly.

**Test:** Navigate between pages, watch `<title>` in DevTools Elements tab.

**Fix:**

```vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

const route = useRoute()

useHead({
  title: () => route.meta.title as string
})
</script>
```

### Google Shows Wrong Title/Description

**Cause:** Google [keeps your `<title>` as-is around 87% of the time](https://developers.google.com/search/blog/2021/09/more-info-about-titles); the rest get rewritten, usually because the tag is missing, duplicated, or doesn't match the page content. There's no equivalent published figure for descriptions, but vague or boilerplate ones get replaced more often.

**Test:** Search for `site:yoursite.com` in Google. Compare displayed titles to your actual meta tags.

**Fix:** Write better titles and descriptions:

- Titles: 50-60 characters, unique per page, include target keyword
- Descriptions: 150-160 characters, compelling copy, match search intent
- Avoid duplicates across pages
- Make them relevant to page content

Google still might rewrite. That's normal. If it rewrites everything, your meta tags are probably too generic or off-topic.

### Hydration Mismatch on Meta Tags

**Cause:** Server and client render different meta tag values (timestamps, random IDs, browser APIs).

**Test:** Look for console warnings: "Hydration completed but contains mismatches."

**Fix:** Use `ClientOnly` wrapper or `data-allow-mismatch`:

```vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { onMounted, ref } from 'vue'

const timestamp = ref('')

onMounted(() => {
  timestamp.value = new Date().toISOString()
})

useHead({
  meta: [
    { property: 'og:updated_time', content: timestamp }
  ]
})
</script>
```

### Content Loads After SSR

**Cause:** Data fetched in `onMounted()` instead of during SSR.

**Test:** View Source shows "Loading..." instead of actual content.

**Fix:** Fetch data before render:

```vue [Client-only fetch]
<!-- ❌ Client-only fetch -->
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { onMounted, ref } from 'vue'

const post = ref({ title: 'Loading...' })

onMounted(() => {
  (async () => {
    post.value = await fetch('/api/post').then(r => r.json())
  })()
})

useHead({ title: () => post.value.title })
</script>
```

```vue [SSR-compatible fetch]
<!-- ✅ SSR-compatible fetch -->
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { ref } from 'vue'

const post = ref(await fetch('/api/post').then(r => r.json()))

useHead({ title: () => post.value.title })
</script>
```

For client-only apps, consider [prerendering critical pages](/learn-seo/vue/spa/prerendering).

## Testing Before Deployment

Don't wait for Google to tell you something's broken. Test locally: right-click → View Page Source and search for critical meta tags, fixing SSR first if they're missing.

### 1. Curl Test

```bash
# Check initial HTML
curl -s https://yoursite.com | grep -A 5 "<head>"

# Check specific meta tag
curl -s https://yoursite.com | grep 'name="description"'
```

Should show full meta tags in output. For staging environments, set up a staging site in Search Console and test URL Inspection there before pushing to production.

### 2. Lighthouse CI

Automate SEO checks in CI/CD:

```bash
npm install -g @lhci/cli

# Run Lighthouse
lhci autorun --collect.url=http://localhost:3000
```

Configure assertion for minimum SEO score.

<tip title="Debugging AI Crawlers">

AI bots like `GPTBot` don't execute JavaScript, so they only ever see your initial HTML response, the same thing View Source shows you. Spoof the user agent in Chrome DevTools (`Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)`) to confirm your content loads without JS and isn't blocked by robots.txt.

</tip>

Nuxt handles SSR and meta tag management automatically, so most of these bugs disappear. See the [Nuxt SEO debugging guide](/learn-seo/nuxt/launch-and-listen/debugging) for framework-specific fixes.

## Checklist

<checklist id="vue-debugging">

- View Source shows your meta tags and page content, not just the DevTools Elements panel
- `createHead()` is set up and installed with `app.use(head)`, using `@unhead/vue/server` for SSR builds
- Titles and descriptions use `useHead()`, never `document.title`
- Fetch data for meta tags before render, not inside `onMounted()`
- No "Hydration completed but contains mismatches" warnings in the console
- URL Inspection in Search Console matches what your server sends

</checklist>
