12/18/2025

Nuxt SEO Optimization: A Complete Guide from Basic Setup to Advanced Techniques

Nuxt SEO Optimization: A Complete Guide from Basic Setup to Advanced Techniques

Nuxt provides powerful SEO tools right out of the box. SSR, automatic pre-rendering, and convenient composables make the framework an excellent choice for projects where search engine visibility is crucial.

In this guide, we'll cover all aspects of technical SEO in Nuxt: from basic meta tags to generating dynamic OG images.

Why Nuxt is Ideal for SEO

Traditional SPAs on Vue have a problem: search crawlers see a blank page before the JavaScript executes. Nuxt solves this in several ways:

ModeDescriptionWhen to Use
SSR (Universal)Server renders HTML for each requestE-commerce, dynamic content
SSG (Static)HTML is generated at build timeBlogs, documentation, landing pages
HybridA combination of SSR and SSG for different routesLarge projects with varied content

Important: For SEO-critical projects, avoid ssr: false mode. Search engines better index server-rendered content.

Quick Start with @nuxtjs/seo

The @nuxtjs/seo module bundles six SEO tools into one package:

  • Sitemap — generates an XML sitemap
  • Robots — manages robots.txt and meta tags
  • OG Image — dynamic images for social media
  • Schema.org — structured JSON-LD data
  • Link Checker — checks for broken links
  • SEO Utils — additional utilities

Installation

npx nuxi module add @nuxtjs/seo

Basic Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/seo'],
  site: {
    url: 'https://example.com',
    name: 'My Site',
    description: 'Site description for search engines',
    defaultLocale: 'en'
  }
})

The site parameter is used by all modules automatically—canonical URLs, sitemaps, and OG tags will get the correct values without code duplication.

Meta Tags: useHead vs useSeoMeta

Nuxt offers two main composables for managing meta tags. Let's look at the differences.

useHead — The Universal Tool

<script setup lang="ts">
useHead({
  title: 'Page Title',
  meta: [
    { name: 'description', content: 'Page description' },
    { property: 'og:title', content: 'OG Title' }
  ],
  link: [
    { rel: 'canonical', href: 'https://example.com/page' }
  ],
  htmlAttrs: { lang: 'en' }
})
</script>

useHead is suitable for any tags in the <head>: scripts, styles, link tags, and html/body attributes.

useSeoMeta — Typed and Secure

<script setup lang="ts">
useSeoMeta({
  title: 'Page Title',
  description: 'Description for search engines',
  ogTitle: 'Title for social media',
  ogDescription: 'Description for social media',
  ogImage: 'https://example.com/og-image.png',
  twitterCard: 'summary_large_image'
})
</script>

Recommendation: Use useSeoMeta for SEO tags—it has 100+ typed parameters and protection against XSS attacks.

Reactive Meta Tags

For dynamic content, pass getter functions:

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

useSeoMeta({
  title: () => article.value?.title,
  description: () => article.value?.excerpt,
  ogImage: () => article.value?.image
})
</script>

Performance Optimization

If meta tags don't need to be reactive, wrap them in a server-only condition:

<script setup lang="ts">
if (import.meta.server) {
  useSeoMeta({
    robots: 'index, follow',
    description: 'Static description',
    ogImage: 'https://example.com/image.png'
  })
}

// Only dynamic tags remain reactive
const title = ref('Dynamic Title')
useSeoMeta({
  title: () => title.value
})
</script>

Open Graph and Social Media

Open Graph determines how a link will look when shared on social media.

Minimum Tag Set

useSeoMeta({
  ogType: 'website',
  ogTitle: 'Page Title',
  ogDescription: 'Short description up to 200 characters',
  ogImage: 'https://example.com/og.png',
  ogUrl: 'https://example.com/page',
  ogLocale: 'en_US',
  
  // Twitter/X
  twitterCard: 'summary_large_image',
  twitterTitle: 'Title for Twitter',
  twitterDescription: 'Description for Twitter',
  twitterImage: 'https://example.com/twitter.png'
})

OG Image Requirements

ParameterRecommendation
Size1200×630 px
FormatPNG or JPG
File SizeUp to 8 MB
Aspect Ratio1.91:1

Dynamic OG Images

The nuxt-og-image module generates images automatically based on Vue components.

Using Built-in Templates

<script setup lang="ts">
defineOgImageComponent('NuxtSeo', {
  title: 'Article Title',
  description: 'Article description',
  theme: '#00dc82',
  colorMode: 'dark'
})
</script>

Custom Template

Create a component in components/OgImage/:

<!-- components/OgImage/BlogPost.vue -->
<template>
  <div class="w-full h-full flex flex-col justify-center items-center bg-gradient-to-br from-green-400 to-blue-500 p-16">
    <h1 class="text-6xl font-bold text-white text-center">
      {{ title }}
    </h1>
    <p class="text-2xl text-white/80 mt-4">
      {{ author }}
    </p>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  title: string
  author: string
}>()
</script>

Usage on a page:

<script setup lang="ts">
defineOgImageComponent('BlogPost', {
  title: 'How to Optimize SEO in Nuxt',
  author: 'John Doe'
})
</script>

Preview

In development mode, add /__og_image__ to the URL for a live preview with hot-reload.

Schema.org and Structured Data

Structured data helps Google show rich snippets in search results.

Basic Setup

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-schema-org'],
  site: {
    url: 'https://example.com',
    name: 'Site Name'
  }
})

Page Types

<script setup lang="ts">
// Homepage
useSchemaOrg([
  defineWebSite({
    name: 'My Blog',
    description: 'A blog about web development'
  }),
  defineOrganization({
    name: 'My Company',
    logo: '/logo.png'
  })
])
</script>
<script setup lang="ts">
// Article page
useSchemaOrg([
  defineArticle({
    headline: 'Article Title',
    description: 'Short description',
    image: '/article-image.jpg',
    datePublished: '2025-01-15',
    dateModified: '2025-01-20',
    author: {
      name: 'Author Name',
      url: 'https://example.com/author'
    }
  })
])
</script>

FAQ Schema

<script setup lang="ts">
useSchemaOrg([
  defineWebPage({ '@type': 'FAQPage' }),
  defineQuestion({
    name: 'How to install Nuxt?',
    acceptedAnswer: 'Run the command npx nuxi init my-app'
  }),
  defineQuestion({
    name: 'What is the current version of Nuxt?',
    acceptedAnswer: 'The current version is Nuxt 4.x'
  })
])
</script>

Tip: Validate your markup using the Google Rich Results Test.

Sitemap and robots.txt

Automatic Sitemap Generation

The @nuxtjs/sitemap module automatically collects static routes:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],
  site: {
    url: 'https://example.com'
  }
})

Dynamic Routes

For pages with dynamic parameters, create an API endpoint:

// server/api/__sitemap__/sitemap.ts
export default defineSitemapEventHandler(async () => {
  const posts = await $fetch('/api/posts')
  
  return posts.map(post => ({
    loc: `/blog/${post.slug}`,
    lastmod: post.updatedAt,
    changefreq: 'weekly',
    priority: 0.8
  }))
})
// nuxt.config.ts
export default defineNuxtConfig({
  sitemap: {
    sources: ['/api/__sitemap__/urls']
  }
})

Configuring robots.txt

// nuxt.config.ts
export default defineNuxtConfig({
  robots: {
    groups: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/admin', '/api', '/private']
      }
    ]
  }
})

Or via a public/_robots.txt file:

User-agent: *
Allow: /
Disallow: /admin
Disallow: /api

Sitemap: https://example.com/sitemap.xml

Important: The module automatically blocks indexing for dev/staging environments.

Image Optimization

The @nuxt/image module is critically important for Core Web Vitals.

Installation

npx nuxi module add @nuxt/image

Usage

<template>
  <!-- Basic usage -->
  <NuxtImg
    src="/images/hero.jpg"
    alt="Hero image"
    width="1200"
    height="630"
    loading="lazy"
    format="webp"
  />
  
  <!-- Responsive sizes -->
  <NuxtImg
    src="/images/product.jpg"
    alt="Product"
    sizes="sm:100vw md:50vw lg:400px"
    :modifiers="{ quality: 80 }"
  />
  
  <!-- Automatic format selection -->
  <NuxtPicture
    src="/images/banner.jpg"
    alt="Banner"
    width="1920"
    height="600"
  />
</template>

Image Providers

// nuxt.config.ts
export default defineNuxtConfig({
  image: {
    provider: 'cloudinary', // or ipx, imgix, vercel, etc.
    cloudinary: {
      baseURL: 'https://res.cloudinary.com/your-cloud/image/upload/'
    },
    quality: 80,
    format: ['webp', 'avif', 'jpeg']
  }
})

i18n and Multilingual SEO

The @nuxtjs/i18n module integrates with SEO modules automatically.

Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n', '@nuxtjs/seo'],
  i18n: {
    locales: [
      { code: 'uk', language: 'uk-UA', name: 'Українська' },
      { code: 'en', language: 'en-US', name: 'English' }
    ],
    defaultLocale: 'uk',
    strategy: 'prefix_except_default'
  }
})

hreflang tags

Use useLocaleHead for automatic generation:

<script setup lang="ts">
const head = useLocaleHead({
  addSeoAttributes: true
})

useHead(head)
</script>

This will add:

  • The lang attribute for <html>
  • hreflang links for all locales
  • A canonical URL for the current locale

Testing and Debugging

Nuxt DevTools

Nuxt SEO integrates with DevTools. Available tabs:

  • SEO — view all meta tags
  • OG Image — preview OG images
  • Schema.org — validate structured data
  • Sitemap — view generated URLs
  • Robots — view current rules

External Tools

ToolPurpose
Google Search ConsoleIndexing, errors
Rich Results TestSchema.org
Meta Tags DebuggerOG tags
PageSpeed InsightsCore Web Vitals

Local OG Tag Testing

To test without deploying, use ngrok:

ngrok http 3000

Then check the URL via metatags.io or the Facebook Sharing Debugger.

Pre-Release Checklist

Basic SEO

  • Unique <title> on every page (50-60 characters)
  • Meta description on every page (150-160 characters)
  • Canonical URL is configured
  • Robots meta tag is correct
  • HTML lang attribute is set

Technical SEO

  • sitemap.xml is generated and accessible
  • robots.txt is configured
  • 404 page returns the correct status code
  • Redirects from HTTP to HTTPS
  • Trailing slashes are consistent

Social Media

  • OG tags on all pages
  • OG image is 1200×630 px
  • Twitter Card tags
  • Verified in Facebook Debugger

Structured Data

  • Organization/Person schema on the homepage
  • Article schema on articles
  • BreadcrumbList for navigation
  • Validated via Rich Results Test

Performance

  • Lighthouse Performance > 90
  • LCP < 2.5s
  • CLS < 0.1
  • Images are optimized (WebP/AVIF)

Sources