Nuxt SEO: Meta, Open Graph, Canonicals and Structured Data
AI generated
<v/>
{ }
Vue.js · Nuxt 3 · SEO · Open Graph · JSON-LD
Nuxt SEO: Meta, Open Graph, Canonicals
and Structured Data, Fully Implemented

Nuxt SEO goes far beyond title tags. useSeoMeta, correct canonical URLs, a complete Open Graph implementation and JSON-LD structured data together determine how well a Nuxt app is understood by search engines and displayed on social media.

16 min read useSeoMeta · useHead · Open Graph · Canonical · JSON-LD · Rich Results Nuxt 3 · Vue 3 · @nuxtjs/seo

1. Nuxt SEO: Why Server-Side Rendering Is Decisive

Nuxt SEO benefits fundamentally from the fact that Nuxt supports server-side rendering (SSR) or static site generation (SSG). Search engine crawlers like Googlebot can execute JavaScript, but they cannot reliably wait for dynamically rendered meta tags. When a classic Vue SPA writes meta tags into the <head> via JavaScript, the crawler may end up indexing the page without complete metadata. Nuxt solves this problem by processing every useSeoMeta call server-side and delivering the finished HTML string with correct meta tags already in place, before any crawler or social scraper touches the page.

The second decisive aspect of Nuxt SEO is contextual correctness. Every page needs individual meta tags that reflect its actual content. A generic title like "My App" on every subpage is counterproductive for Nuxt SEO: search engines cannot understand what each page is about, and users cannot distinguish between different pages of your app in the search results. With useSeoMeta and reactive composables, Nuxt SEO can be implemented so that every page automatically gets the right title, the right description and correct structured data, fully automatically, from the same data that also drives the page content.

2. useSeoMeta: The Modern Approach to Meta Tags

The useSeoMeta composable has been the recommended way to set meta tags since Nuxt 3.3. Compared to a direct useHead call, it offers TypeScript autocompletion for all known meta properties and prevents common mistakes like duplicate name attributes or incorrect property names. A single useSeoMeta call with the most important fields covers basic SEO, Open Graph and Twitter Cards at the same time, without the boilerplate that manual useHead configurations produce.

Reactive values work in useSeoMeta through getter functions: instead of a static string, you pass () => pageData.value?.title as the value. Nuxt automatically re-evaluates the meta tags whenever pageData changes, for example after API data has loaded. This is especially important for Nuxt SEO on detail pages such as product pages or blog articles, where the title and description come dynamically from a CMS API. Without getter syntax, the value stays static at its initial value, regardless of any later data changes.


// pages/blog/[slug].vue - Dynamic SEO meta from API data
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)

// useSeoMeta with reactive getters - updates when article.value changes
useSeoMeta({
  title: () => article.value?.title ?? 'Blog | mironsoft.de',
  description: () => article.value?.excerpt,
  ogTitle: () => article.value?.title,
  ogDescription: () => article.value?.excerpt,
  ogImage: () => article.value?.coverImage?.url,
  ogType: 'article',
  ogUrl: () => `https://mironsoft.de/blog/${route.params.slug}`,
  articlePublishedTime: () => article.value?.publishedAt,
  articleModifiedTime: () => article.value?.updatedAt,
  articleAuthor: () => article.value?.author?.name,
  twitterCard: 'summary_large_image',
  twitterTitle: () => article.value?.title,
  twitterDescription: () => article.value?.excerpt,
})

3. Implementing Open Graph Completely

Open Graph determines how a page is displayed on Facebook, LinkedIn, WhatsApp, Telegram and other platforms that generate link previews. A complete Open Graph implementation for Nuxt SEO includes at least: og:title, og:description, og:image, og:url, og:type and og:site_name. The og:image should be at least 1200x630 pixels and served over HTTPS. Platforms cache these images aggressively, so consistent URLs without query parameters matter.

For article pages, the Open Graph schema extends with the article namespace: article:published_time, article:modified_time, article:author and article:section give platforms and search engines precise signals about the content type. In useSeoMeta, these properties are called articlePublishedTime, articleModifiedTime and so on; the composable translates them automatically into the correct meta property tags. For Nuxt SEO on e-commerce pages, there is additionally a product namespace for prices and availability, which is relevant for shopping previews.

4. Twitter Cards and Social Media Previews

Twitter (X) uses its own meta schema in addition to Open Graph. The most important Twitter Card types for Nuxt SEO are summary (small image on the left) and summary_large_image (large image on top). For most content pages, summary_large_image is the better choice because it draws more visual attention. If the Twitter Card tags are missing, Twitter falls back to Open Graph tags, but not all OG fields are mapped correctly. Explicit twitter:* tags are therefore important for controlled display.

A frequently overlooked detail in Nuxt SEO for social media: the og:image URL must be absolute, not relative. A relative path like /images/cover.jpg will not be resolved by social scrapers because they either do not know the base URL or do not handle domain-relative paths. In Nuxt, you build the correct absolute URL by combining the runtime config value siteUrl with the relative image path: \`${config.public.siteUrl}${article.coverImage.path}\`. The same applies to og:url and the canonical URL.


// composables/useSeoDefaults.ts - Shared SEO defaults for all pages
export function useSeoDefaults(overrides: Partial<SeoConfig> = {}) {
  const config = useRuntimeConfig()
  const route = useRoute()

  const defaults = {
    siteName: 'mironsoft.de',
    siteUrl: config.public.siteUrl,
    defaultImage: `${config.public.siteUrl}/images/og-default.jpg`,
  }

  // Canonical URL: always absolute, always current route
  useHead({
    link: [
      {
        rel: 'canonical',
        href: () => `${defaults.siteUrl}${route.path}`,
      },
    ],
  })

  useSeoMeta({
    ogSiteName: defaults.siteName,
    ogImage: () => overrides.ogImage ?? defaults.defaultImage,
    ogImageWidth: 1200,
    ogImageHeight: 630,
    ogImageAlt: () => overrides.title ?? defaults.siteName,
    twitterCard: 'summary_large_image',
    ...overrides,
  })
}

5. Canonical URLs: Avoiding Duplicates

Canonical URLs are one of the most important, yet most frequently misimplemented, elements of Nuxt SEO. They signal to search engines which URL is the "canonical" version of a page when the same content is reachable under multiple URLs, for example with and without a trailing slash, with different query parameters, or across multiple domains. Without a correct canonical, Google can split a page's ranking across several URL variants instead of consolidating it.

In Nuxt, the canonical is best set in a global layout or a reusable composable that automatically uses the current route path. What matters for Nuxt SEO: the canonical must always be the absolute URL of the preferred version, with protocol and domain, without a trailing slash (or consistently with a trailing slash, depending on configuration), and without pagination parameters. Paginated pages like /blog?page=2 get their own canonical URL that explicitly points to the paginated version, not always to page 1. For this, there is the prev/next pattern with link rel="prev" and link rel="next".

6. Structured Data with JSON-LD

JSON-LD is the format Google recommends for structured data and the most important building block for rich results in Google's search results. For Nuxt SEO, this means: article pages get an Article or BlogPosting schema, product pages get a Product schema with prices and ratings, FAQ pages get an FAQPage schema. This structured data enables rich result formats such as star ratings, prices shown directly in the search results, and expanded FAQ snippets.

In Nuxt, the most elegant way to add JSON-LD is with useHead and a script tag: useHead({ script: [{ type: 'application/ld+json', innerHTML: JSON.stringify(schema) }] }). The schema itself is built reactively as a computed property so that it updates automatically whenever the data changes. One mistake that damages Nuxt SEO: JSON-LD with incomplete required fields or incorrect data types. Google's Rich Results Test shows exactly which fields are missing or wrong, this tool should be consulted with every implementation.


// pages/blog/[slug].vue - JSON-LD structured data for BlogPosting
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
const config = useRuntimeConfig()

const articleSchema = computed(() => ({
  '@context': 'https://schema.org',
  '@type': 'BlogPosting',
  headline: article.value?.title,
  description: article.value?.excerpt,
  image: article.value?.coverImage?.url,
  datePublished: article.value?.publishedAt,
  dateModified: article.value?.updatedAt,
  author: {
    '@type': 'Person',
    name: article.value?.author?.name,
    url: `${config.public.siteUrl}/autoren/${article.value?.author?.slug}`,
  },
  publisher: {
    '@type': 'Organization',
    name: 'Mironsoft',
    url: config.public.siteUrl,
    logo: { '@type': 'ImageObject', url: `${config.public.siteUrl}/logo.png` },
  },
  mainEntityOfPage: { '@type': 'WebPage', '@id': `${config.public.siteUrl}/blog/${route.params.slug}` },
}))

useHead({
  script: [{ type: 'application/ld+json', innerHTML: () => JSON.stringify(articleSchema.value) }],
})

7. Dynamic SEO Data from API and CMS

When SEO data comes from a headless CMS like Contentful, Storyblok or Directus, correct timing is decisive for Nuxt SEO. With useFetch and useAsyncData, Nuxt loads the data server-side before rendering, so meta tags end up in the finished HTML string. This is the crucial difference to client-only fetching: with onMounted or watch, the data is only loaded in the browser, and the server then delivers empty or default meta tags, which is what search engines index.

A common mistake with Nuxt SEO and dynamic data: the useSeoMeta call is not placed within the await block of the data fetch. If the fetch operation is asynchronous and useSeoMeta is called synchronously before the data is available, the getter functions get initialized with undefined values. This problem is elegantly solved with reactive getter functions (() => data.value?.title) instead of static values: the getters are re-evaluated at render time once the data becomes available.

8. Robots Meta and Sitemap Integration

The robots meta tag controls which pages get included in the search index. For Nuxt SEO, three values are particularly relevant: index, follow (default, page gets indexed), noindex, follow (do not index the page, but follow its links) and noindex, nofollow (exclude the page from the index entirely and do not follow its links). Pages like /thank-you after a form submission, the cart, or internal search result pages should be marked with noindex so they do not appear in the search index and do not waste crawl budget.

The sitemap is the counterpart to robots meta: it lists all the URLs that should be indexed, together with a modification date and priority. The @nuxtjs/sitemap package generates the sitemap automatically from the Nuxt routes and can be extended for dynamic routes with a fetch function: routes: async () => (await fetchAllArticleSlugs()).map(slug => `/blog/${slug}`). Sitemap and Nuxt SEO meta tags are two sides of the same coin: the sitemap tells search engines which pages exist, the meta tags tell them how to understand those pages.

9. Nuxt SEO Approaches Compared

There are several ways to implement Nuxt SEO. The table compares the main approaches by type, use case and limitations.

Approach Use Case Reactivity Limitation
useSeoMeta All standard meta tags Yes, via getters No custom HTML
useHead Custom tags, scripts, links Yes, via getters More boilerplate than useSeoMeta
nuxt.config head Global defaults No, static No access to runtime values
definePageMeta Static page meta No, compile-time No access to Pinia/API
@nuxtjs/seo Full SEO package Yes External dependency, more configuration

For most Nuxt projects, useSeoMeta with reactive getters is the best choice for page-specific meta tags, complemented by useHead for JSON-LD and canonical links. Global defaults go into nuxt.config.ts under app.head. The @nuxtjs/seo package is worthwhile for projects that want a complete Nuxt SEO solution with sitemap, robots.txt and social image generation from a single package.

Mironsoft

Vue.js · Nuxt 3 · SEO Architecture · Structured Data

Need a Complete Nuxt SEO Implementation for Your App?

We implement complete Nuxt SEO solutions, from useSeoMeta and Open Graph through canonical URLs to JSON-LD structured data for rich results in Google.

SEO Audit

Analysis of existing Nuxt apps for meta tag errors, missing canonicals and incomplete Open Graph implementation

JSON-LD Implementation

Structured data for articles, products, FAQs and organizations, optimized for rich results in Google

CMS Integration

Reactively integrating SEO metadata from Contentful, Storyblok or Directus into Nuxt

10. Summary

Complete Nuxt SEO covers four layers: basic meta tags (title, description, robots), social meta tags (Open Graph and Twitter Cards), canonical URLs, and JSON-LD structured data. The useSeoMeta composable with reactive getter functions is the most efficient way to set all meta tags reactively and in a type-safe manner. Canonical URLs must always be absolute and set consistently to avoid duplicate content problems. JSON-LD enables rich results in Google and should be implemented for every page with structured content.

The biggest lever for Nuxt SEO lies in getting the timing right: meta tags must be present in the server-rendered HTML, not loaded dynamically in the browser afterward. useFetch and useAsyncData combined with getter functions in useSeoMeta guarantee that. Social media scrapers and search engine crawlers then see the same fully populated HTML code that the end user gets, which is the foundation good rankings and compelling social media previews are built on.

Nuxt SEO: The Essentials at a Glance

useSeoMeta

Reactive getter functions instead of static strings, meta tags update automatically when API data changes.

Canonical URLs

Always absolute, with protocol and domain. Apply automatically to every page via a global composable.

JSON-LD

Add via useHead as a script tag, built as a computed property, essential for rich results in Google.

SSR Timing

useFetch instead of onMounted for data fetching, meta tags must be present in the server-rendered HTML.

11. FAQ: Nuxt SEO Meta, Open Graph and Structured Data

1useSeoMeta vs. useHead?
useSeoMeta is type-safe and optimized for SEO tags. useHead covers all head elements. Use useSeoMeta for SEO meta tags, useHead for scripts and links.
2Why does og:image need to be absolute?
Social media scrapers do not resolve relative URLs. Always provide a complete URL with protocol and domain.
3Reactive meta tags with API data?
Getter functions: () => data.value?.title instead of static strings. Nuxt re-evaluates them at render time whenever data loads in.
4What is a canonical URL?
Shows search engines the preferred URL version. Prevents PageRank from being split across URL variants. Always set it as absolute.
5Which pages benefit from JSON-LD?
Articles, products, FAQs, events, recipes, any page with structured content. Enables rich results in Google.
6Client-only rendering and SEO?
Scrapers do not execute JavaScript. Meta tags must be present in the server-rendered HTML, Nuxt SSR/SSG ensures that.
7Pagination and duplicate content?
Every paginated page gets its own canonical. Additionally set rel="prev" and rel="next" for correct page sequence signals.
8Which pages need noindex?
Thank-you pages, cart, checkout, login, internal search results, any page without standalone editorial content.
9What does the Rich Results Test check?
JSON-LD for completeness and errors, which rich result types are supported and which required fields are missing. Available at search.google.com/test/rich-results.
10Sitemap for dynamic routes?
@nuxtjs/sitemap with an asynchronous routes function: load all slugs from the API and return them as paths.