---
title: "How to Debug Vue SEO Issues"
description: "Investigate Vue 3 head setup, reactive values, and rendering with initial HTML checks and Search Console observations."
canonical_url: "https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging"
last_updated: "2026-10-05"
---

> Index of every page on this site: [llms.txt](https://nuxtseo.com/llms.txt)

::key-takeaways
- View Source shows what Google sees first. Inspect Element shows post-JavaScript DOM
- Inspect hydration warnings and actual head values; a DOM mismatch alone does not establish a metadata defect
- A successful URL Inspection live test shows tested rendering; stored indexed information is a separate observation
::

View Source shows the initial HTML. [Google can also render JavaScript](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). Compare initial and rendered output, setup, data loading, and route configuration before identifying a cause.

## 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>`{lang="html"} or `<meta name="description"`. If content is absent there, check whether it appears after rendering or never loads.

```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. Initial HTML exposes content before JavaScript execution. Google does not publish a fixed rendering or indexing delay.

## 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
<!-- Direct title mutation bypasses Unhead -->
<script setup lang="ts">
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

watch(() => route.path, () => {
  document.title = route.meta.title as string // Can change the browser title independently of Unhead
})
</script>
```

Fix: use reactive inputs with `useHead()`{lang="ts"}. These setup excerpts require installed Unhead and Vue Router plugins, with a string route.meta.title on each page.

```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>`{lang="html"} section in Elements tab:

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

If tags do not change, inspect reactive inputs, data, conflicting entries, and navigation errors. The symptom alone does not identify one cause.

## Inspect Hydration Warnings and Head Output

During hydration, Vue attaches reactivity and event listeners to server-rendered HTML. 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** can trigger browser correction before hydration. Compare actual response and parsed DOM against [Vue’s hydration guidance](https://vuejs.org/guide/scaling-up/ssr.html#hydration-mismatch).

**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** may depend on browser APIs. Inspect SSR support and avoid server imports with browser-only side effects. Plain Vue does not provide a built-in ClientOnly component; supply an explicit client-only integration if needed.

### Suppressing Mismatches in Vue 3.5+

For an expected rendered DOM difference, follow [Vue’s data-allow-mismatch documentation](https://vuejs.org/guide/scaling-up/ssr.html#suppressing-hydration-mismatches). Its documented values are categories or an empty attribute, not a boolean true binding. Suppression does not correct inconsistent data or establish valid metadata.

Compare deployed HTML transformations and asset versions with the local response before blaming a minifier or browser cache. Do not disable services based on a warning alone.

## Unhead Configuration Mistakes

Check plugin installation, reactive inputs, and the response integration separately.

### Missing createHead() Setup

Unhead requires initialization. With Unhead 3.4.2, calling useHead without its Vue plugin can throw a missing-context error. It does not silently establish a head entry.

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

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

Fix: Call `createHead()`{lang="ts"} 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 an SSR app’s server entry, create a server head per request and install it before component setup. Render its tags into the returned HTML. The client entry needs its own client head during hydration. Follow the [complete Vite SSR guide](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr); changing an import alone does not establish SSR.

### Wrong Import Paths (Unhead v2)

Current Unhead 3.4.2 exposes createHead through environment subpaths:

```ts
// Browser entry
import { createHead } from '@unhead/vue/client'
```

```ts
// Server entry
import { createHead } from '@unhead/vue/server'
```

These are separate entry-point references. Install the plugin in each corresponding app instance. Older release imports differ; use the guide and exports for the version you installed.

### Not Using SSR

A client head can update the browser DOM. To place those dynamic tags in an initial HTML response, use an SSR/SSG response pipeline and serialize the head output. Static HTML can also supply tags. Google can render client JavaScript; a client-only head is not automatically invisible to Search. Follow the [complete rendering setup](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr), not an import-only switch.

### 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 { ref } from 'vue'

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

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

### Duplicate Meta Tags

Unhead deduplicates matching tags. Multiple entries can still conflict; inspect which value survives:

```vue
<!-- Matching descriptions are deduplicated -->
<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()`{lang="ts"} 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

If tags are absent, inspect registration, loaded data, and the response pipeline. Absence alone does not establish the cause.

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()`{lang="ts"} calls.

### 2. Console Errors

JavaScript errors can interrupt affected setup or head 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. Inspect the failing setup or navigation before choosing a repair.

### 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>`{lang="html"} element
- Document has meta description
- Links have descriptive text

Basic audits do not prove metadata describes the page accurately. Review its actual values.

## Google Search Console URL Inspection

URL Inspection distinguishes stored indexed information from a current live test.

### 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

Inspect stored indexing state and crawl details separately from Test live URL. For a successful live test, open View tested page for tested HTML and a screenshot. A live success does not establish indexing. Follow [Google’s URL Inspection documentation](https://support.google.com/webmasters/answer/9012289).

### View Crawled Page

Read available stored crawl information for the indexed URL. A screenshot is available for a successful live test, not the indexed crawl. Differences from your browser can reflect data, timing, resources, or personalization; they do not establish one rendering defect.

### 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:

- Successful live test → Tested current rendering
- Stored indexed information → Available observations from Google’s recorded crawl

### 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. Indexing remains a separate decision.

Google limits daily index requests. The live-test inspection limits do not establish the index-request scope. The [Indexing API](https://developers.google.com/search/apis/indexing-api/v3/quickstart) only covers JobPosting or livestream VideoObject containing BroadcastEvent. Its initial testing quota is 200 publish requests per project per day; it is not a general bulk-indexing route.

## Common Vue SEO Debugging Scenarios

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

**Possible causes:** client-only registration, delayed data, failed setup, or a client-rendered route.

**Test:**

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

If curl shows an empty title, inspect registration, data loading, and rendering configuration. If the route needs initial HTML, check its response pipeline.

**Conditional repair:** use SSR, SSG, or static tags when the route needs initial HTML. See [Vue rendering modes](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering) guide.

### Meta Tags Don't Update on Navigation

**Possible causes:** snapshot values, conflicting entries, failed navigation, or direct title mutation. This example requires installed plugins and string route.meta.title values.

**Test:** Navigate between pages, watch `<title>`{lang="html"} 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

Google can generate title links and snippets from visible content. A changed result does not prove an implementation failure. Compare the response title, rendered head, visible heading, and search result.

Use descriptive [titles](https://nuxtseo.com/learn-seo/vue/mastering-meta/titles) and [descriptions](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions) matching the page. Google does not prescribe universal 50–60 or 150–160 character limits.

### Hydration Mismatch on Meta Tags

Inspect the actual response, rendered head, and warning before attributing a mismatch to metadata. Use stable page data for head values. Vue’s DOM mismatch suppression does not establish correct head output.

### Content Loads After SSR

A client-only fetch starts after mounting, so its data is absent from the initial response. Missing data or failed setup can produce a similar symptom. A relative URL is not automatically an app URL in Node.

Use the [initial catalog rendering excerpt](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues#vue-spa-specific-issues-content-loaded-after-initial-render) with app-owned server-loaded props, and the [complete Vite SSR setup](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr) for entries and response serialization. Preserve error and missing-data distinctions. Loading text does not expose the actual content.

## 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, checking registration, data loading, and the response pipeline if they are 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. Private staging requires authentication. Use direct authorized response inspection there; Search Console cannot fetch a private page without access.

### 2. Lighthouse CI

Use the [Lighthouse CI getting-started procedure](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/getting-started.md) with an installed CLI and running site. A minimum SEO score does not prove every metadata value or indexing outcome.

::tip{title="Debugging AI Crawlers"}
Inspect initial HTML for clients that do not execute JavaScript. Changing a browser’s user agent does not disable JavaScript or prove a vendor bot’s capabilities. Inspect provider documentation separately.
::

Nuxt enables SSR and integrates a head manager. Data, reactive values, and deployment responses still need verification. See the [Nuxt SEO debugging guide](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/debugging) for framework-specific fixes.

## Checklist

::checklist{#vue-debugging}
- When the route needs initial HTML, verify its content and tags in View Source
- `createHead()`{lang="ts"} is set up and installed with `app.use(head)`{lang="ts"}, using `@unhead/vue/server` for SSR builds
- Titles and descriptions use `useHead()`{lang="ts"}, never `document.title`
- When tags need initial HTML, load their data before server rendering
- No "Hydration completed but contains mismatches" warnings in the console
- Compare initial responses, successful live tests, and stored indexed information without treating them as identical observations
::

## Sitemap

See the full [sitemap](https://nuxtseo.com/sitemap.md) for all pages.
