Nuxt 3 Open Graph Meta Tags: useHead, useSeoMeta, and nuxt-og-image

How to add og:title, og:image, og:description, and Twitter card meta tags in Nuxt 3 using useHead, useSeoMeta, and the nuxt-og-image module for dynamic social preview generation.

Updated March 2026

Option 1: useSeoMeta (Recommended for Nuxt 3)

Nuxt 3's useSeoMeta composable is the cleanest way to set OG tags — fully typed with TypeScript and works with SSR out of the box:

<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useAsyncData(route.params.slug as string, () =>
  queryContent(route.params.slug as string).findOne()
);

useSeoMeta({
  title: post.value?.title,
  description: post.value?.description,
  
  // Open Graph
  ogTitle: post.value?.title,
  ogDescription: post.value?.description,
  ogImage: post.value?.ogImage ?? "https://yoursite.com/og-default.png",
  ogUrl: `https://yoursite.com${route.fullPath}`,
  ogType: "article",
  
  // Twitter Card
  twitterCard: "summary_large_image",
  twitterTitle: post.value?.title,
  twitterDescription: post.value?.description,
  twitterImage: post.value?.ogImage ?? "https://yoursite.com/og-default.png",
});
</script>

<template>
  <article>
    <h1>{{ post?.title }}</h1>
    <!-- content -->
  </article>
</template>

Option 2: useHead for More Control

<script setup lang="ts">
useHead({
  title: "Your Page Title",
  meta: [
    { name: "description", content: "Page description" },
    
    // Open Graph
    { property: "og:title",       content: "Your Page Title" },
    { property: "og:description", content: "Page description" },
    { property: "og:image",       content: "https://yoursite.com/og.png" },
    { property: "og:url",         content: "https://yoursite.com/page" },
    { property: "og:type",        content: "article" },
    
    // Twitter Card
    { name: "twitter:card",        content: "summary_large_image" },
    { name: "twitter:title",       content: "Your Page Title" },
    { name: "twitter:description", content: "Page description" },
    { name: "twitter:image",       content: "https://yoursite.com/og.png" },
  ],
  link: [
    { rel: "canonical", href: "https://yoursite.com/page" },
  ],
});
</script>

Global Defaults in nuxt.config.ts

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      title: "My Site",
      meta: [
        { name: "description", content: "My site default description" },
        { property: "og:type",  content: "website" },
        { property: "og:image", content: "https://yoursite.com/og-default.png" },
        { property: "og:image:width",  content: "1200" },
        { property: "og:image:height", content: "630" },
        { name: "twitter:card", content: "summary_large_image" },
      ],
    },
  },
})

Per-page useSeoMeta or useHead calls override these defaults — they don't stack, they replace.

nuxt-og-image Module (Dynamic Generation)

For automatically generated OG images from a template (no manual image creation), use the community nuxt-og-image module:

# Install
npm install nuxt-og-image

# nuxt.config.ts
export default defineNuxtConfig({
  modules: ["nuxt-og-image"],
  ogImage: {
    defaults: {
      component: "OgImageDefault", // your component
      width: 1200,
      height: 630,
    },
  },
});

Create a component in components/OgImage/Default.vue:

<!-- components/OgImage/Default.vue -->
<template>
  <div class="og-wrapper">
    <div class="brand">yoursite.com</div>
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
  </div>
</template>

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

<style scoped>
.og-wrapper {
  width: 1200px;
  height: 630px;
  background: #0a0a0a;
  color: white;
  display: flex;
  flex-direction: column;
  padding: 80px;
  font-family: Inter, sans-serif;
}
</style>

Then use it on any page:

<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
defineOgImage({
  component: "OgImageDefault",
  title: post.value?.title,
  description: post.value?.description,
});
</script>

Nuxt 3 vs Nuxt 2 OG Tag Differences

ApproachNuxt 2Nuxt 3
Per-page metahead() option in componentuseSeoMeta() / useHead()
Global defaultsnuxt.config.js head:nuxt.config.ts app.head
Reactive metaManual via head()Auto with useSeoMeta()
TypeScript supportLimitedFull
Dynamic OG imagesnuxt-social-metanuxt-og-image module

Verify SSR: Does Your OG Image Appear in Source?

# Check what Nuxt is server-rendering (must include OG tags)
curl -s https://yoursite.com/blog/your-post | grep -E "og:|twitter:"

# Expected output:
# <meta property="og:title" content="..." />
# <meta property="og:description" content="..." />
# <meta property="og:image" content="..." />
# <meta name="twitter:card" content="summary_large_image" />

If OG tags don't appear in the curl output, your Nuxt app may not be SSR-rendering that page correctly. Check your rendering mode (ssr: true in nuxt.config.ts) and ensure useAsyncData resolves before the head is set.

Test your Nuxt 3 OG tags →

Preview my Nuxt OG tags →