Nuxt 3 SEO & Meta Management

Nuxt 3 renders universally by default, which means it already does the hardest part of JavaScript SEO — putting content and metadata in the HTTP response — without extra configuration. The work that remains is choosing the right rendering mode per route and setting metadata cleanly per page. This guide maps Nuxt’s SEO surface and sits within framework-specific SEO implementations, applying the rendering-strategy decisions in Nuxt’s idiom.

Prerequisites

  • Nuxt 3 with the default universal (SSR) rendering, or Nitro configured for your hosting target.
  • A clear per-route rendering intent (SSR, prerender/SSG, or ISR).
  • Page components using useSeoMeta / useHead, or definePageMeta for route-level config.
  • Search Console access to verify rendered metadata.
The four prerequisites before configuring Nuxt SEO Universal SSR left on, a per-route rendering intent, the useSeoMeta and useHead composables, and Search Console access together make the app ready to set metadata. Have these in place before touching metadata Universal SSR on not ssr: false globally Per-route intent SSR · SSG · ISR chosen per route Head composables useSeoMeta / useHead ready Search Console access to verify rendered head Ready to set per-route metadata
With SSR left on, a per-route rendering intent, the head composables, and Search Console access, the app is ready to set metadata cleanly.

How it breaks

The typical Nuxt SEO regression is disabling SSR globally (ssr: false) to fix a build issue, turning the whole app into a client-rendered SPA — so every route reverts to the empty-shell failure Nuxt was protecting you from. A subtler one is setting metadata in onMounted, which runs only on the client and never makes it into the server response.

Nuxt 3 SEO surface feeding the server-rendered head Route rules, useSeoMeta, and useHead all feed into the universally rendered document head delivered in the HTTP response. routeRules (mode) useSeoMeta (tags) useHead (JSON-LD) Universal render server head crawler
Route rules and the head composables all resolve during the universal render, so the crawler receives complete metadata.

Step-by-step fix

  1. Keep SSR on; set the mode per route with route rules.

    // nuxt.config.ts
    export default defineNuxtConfig({
      routeRules: {
        '/':         { prerender: true }, // SSG
        '/blog/**':  { isr: 3600 },       // ISR
        '/shop/**':  { ssr: true },       // SSR
      },
    });
  2. Set standard metadata with useSeoMeta. It is typed and runs during SSR.

    <script setup>
    const { data: post } = await useFetch(`/api/posts/${useRoute().params.slug}`);
    useSeoMeta({
      title: () => post.value.title,
      description: () => post.value.summary,
      ogTitle: () => post.value.title,
      ogImage: () => post.value.ogImage,
    });
    </script>
  3. Add structured data with useHead. Use it for JSON-LD and link tags — detailed in dynamic meta tags with useHead in Nuxt 3.

  4. Never set SEO metadata in onMounted — it runs client-only and misses the server response.

The order of the Nuxt SEO fix, and the one call to avoid Set the rendering mode with route rules, then standard tags with useSeoMeta, then structured data with useHead, and never set metadata in onMounted because it runs client-only. Three steps in order, plus the one to avoid 1 routeRules — set the rendering mode per route 2 useSeoMeta — standard title, description and og: tags during SSR 3 useHead — JSON-LD structured data and link tags Never in onMounted — client-only, missing from the server response ✗
The fix is ordered — route rules, then useSeoMeta, then useHead — and deliberately never routes metadata through onMounted.

Gotchas & edge cases

  • Global ssr: false. Turns the app into a CSR SPA; scope client-only rendering to route rules instead.
  • await before useSeoMeta. Ensure data is awaited so the title is resolved during SSR, not after.
  • Duplicate tags from manual useHead. Prefer useSeoMeta for standard tags to avoid conflicting entries.
  • Canonical on parameterized routes. Set a clean canonical via useHead for filtered or paginated routes — see canonical URL management.
Where the title tag ends up: onMounted versus useSeoMeta Setting metadata in onMounted writes it to the browser DOM only, so the HTTP response the crawler reads stays empty, whereas useSeoMeta resolves during SSR and ships the title in the response. Only tags resolved during SSR reach the crawler onMounted() — client only HTTP response head empty browser DOM title set late crawler reads response ✗ title missing useSeoMeta() — during SSR HTTP response title in head browser DOM already correct crawler reads response ✓ title present
onMounted writes the title to the browser only, so the response the crawler reads is empty; useSeoMeta resolves during SSR and puts it in the response.

Validation checklist

Each validation check and the SEO guarantee it confirms A curl proves the tags are in the response, URL Inspection proves the render matches, the shell check proves no route reverted to CSR, the Rich Results Test proves JSON-LD is valid, and the canonical check proves parameterized routes consolidate. Every check confirms one SEO guarantee curl each route title + description in the response GSC URL Inspection rendered head matches live no client-only shell every route stays server-rendered Rich Results Test JSON-LD validates canonical check parameterized routes consolidate
Each validation check confirms a specific SEO guarantee, from the tags being in the response to canonicals consolidating parameterized routes.

Performance & crawl-budget notes

Because Nuxt renders metadata during SSR, routes index in the first wave and skip the render queue, preserving crawl budget. Route rules let you reserve full SSR for genuinely dynamic routes and prerender the rest, minimizing server cost while keeping everything crawlable.

Nuxt route rules mapping URL patterns to rendering modes Each routeRules glob maps a URL pattern to a rendering mode: the homepage prerenders to static HTML, the blog uses ISR, and the shop stays fully server-rendered. One routeRules entry sets the rendering mode per URL pattern Route glob Rendering mode / prerender (SSG) static marketing shell /blog/** isr: 3600 rebuilt on a schedule /shop/** ssr rendered per request
Route rules attach a rendering mode to each URL pattern, so one config maps the whole app onto SSG, ISR, and SSR.

Go deeper

Where the useHead deep-dive extends this overview This overview branches into the dedicated useHead guide, which covers data-driven titles, social tags, and JSON-LD in depth. The overview feeds one deeper guide This overview Nuxt SEO surface useHead deep-dive data-driven titles social tags JSON-LD
This overview branches into the dedicated useHead guide, which covers data-driven titles, social tags, and JSON-LD in depth.

Frequently Asked Questions

Is Nuxt 3 good for SEO by default? Yes. Nuxt 3 renders universally by default, so routes ship server-rendered HTML with metadata in the response. The SEO work is mostly choosing the right per-route rendering mode with route rules and setting per-page metadata with useSeoMeta or useHead.

Should I use useHead or useSeoMeta in Nuxt 3? Use useSeoMeta for standard SEO and social tags — it is typed and maps directly to title, description, and og: properties. Use useHead for anything useSeoMeta does not cover, such as link tags, scripts, or JSON-LD structured data.

← Back to Framework-Specific SEO Implementations