Complete Guide to Open Graph Meta Tags in Nuxt 3 with useSeoMeta
Open Graph (OG) meta tags control how your content appears when shared on social platforms like Facebook, LinkedIn, Twitter/X, and Discord. In Nuxt 3, the useSeoMeta composable provides a modern, type-safe way to configure these tags and maximize your social media presence.
Why Open Graph Matters
When someone shares your link on social media, platforms crawl your page for Open Graph meta tags to generate rich preview cards. A well-configured OG image and title can double your click-through rate compared to plain text links.
Without proper OG tags, social platforms display generic previews with broken images or missing descriptions—wasting your content's viral potential.
The Modern Approach: useSeoMeta
Nuxt 3 introduced useSeoMeta, a composable that handles Open Graph tags with full TypeScript support and automatic deduplication. This is the recommended method over manual <meta> tags or the older useHead approach for SEO properties.
Basic Implementation
Use useSeoMeta in any page or component's <script setup>:
<script setup lang="ts">
useSeoMeta({
// Basic Open Graph tags
ogTitle: 'Your Engaging Page Title',
ogDescription: 'A compelling description that makes people want to click',
ogType: 'website',
ogUrl: 'https://example.com/page',
ogImage: 'https://example.com/og-image.png',
// Twitter/X Card tags
twitterCard: 'summary_large_image',
twitterTitle: 'Your Engaging Page Title',
twitterDescription: 'A compelling description that makes people want to click',
twitterImage: 'https://example.com/og-image.png'
})
</script>
Key Properties
| Property | Purpose | Best Practices |
|---|---|---|
ogTitle |
Social share title | 40-60 chars; can differ from page <title> |
ogDescription |
Social share description | 120-160 characters for optimal display |
ogType |
Content type | website, article, book, profile, video.movie |
ogUrl |
Canonical URL | Absolute URLs only; match rel="canonical" |
ogImage |
Preview image | Critical for CTR—1200x630px min, absolute URL |
ogImageAlt |
Image alt text | Accessibility and fallback display |
twitterCard |
Twitter card type | Use summary_large_image for rich previews |
Open Graph Images
The og:image property has the biggest impact on engagement. Follow these specifications:
Image Requirements
- Dimensions: 1200x630px minimum (1200x630 or 1200x1200 recommended)
- File size: ~1MB maximum (smaller loads faster)
- Format: PNG for graphics, JPEG for photos
- URL: Must be absolute (
https://example.com/image.png) - Alt text: Always include
ogImageAltfor accessibility
Image Configuration Example
<script setup lang="ts">
useSeoMeta({
ogImage: 'https://example.com/og-image.png',
ogImageAlt: 'Descriptive text for the image',
ogImageWidth: '1200',
ogImageHeight: '630',
ogImageType: 'image/png',
// For Twitter/X
twitterCard: 'summary_large_image',
twitterImage: 'https://example.com/og-image.png',
twitterImageAlt: 'Descriptive text for the image'
})
</script>
Dynamic and Reactive Tags
Nuxt's useSeoMeta supports reactive values—perfect for dynamic routes like blog posts or product pages:
<script setup lang="ts">
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
useSeoMeta({
ogTitle: () => article.value?.title,
ogDescription: () => article.value?.excerpt,
ogImage: () => `https://example.com/api/og?title=${encodeURIComponent(article.value?.title)}`,
ogType: 'article',
ogUrl: () => `https://example.com${route.fullPath}`,
// Article-specific properties
articlePublishedTime: () => article.value?.publishedAt,
articleAuthor: () => article.value?.author,
articleTag: () => article.value?.tags
})
</script>
Critical Warning: Never set OG tags in onMounted() or client-side only code. Social media crawlers don't execute JavaScript, so tags must be rendered server-side.
Global Defaults and Page Overrides
Global Configuration
export default defineNuxtConfig({
app: {
head: {
meta: [
{ property: 'og:site_name', content: 'Your Site Name' },
{ property: 'og:type', content: 'website' },
{ property: 'og:locale', content: 'en_US' }
]
}
}
})
Page-Level Overrides
<!-- pages/about.vue -->
<script setup lang="ts">
useSeoMeta({
ogTitle: 'About Us - Your Site Name',
ogDescription: 'Learn about our mission and team',
ogImage: 'https://example.com/about-og.png'
})
</script>
Or set defaults in your root layout:
<!-- layouts/default.vue -->
<script setup lang="ts">
const config = useRuntimeConfig()
useSeoMeta({
ogSiteName: 'Your Site Name',
ogType: 'website',
ogLocale: 'en_US'
})
</script>
Multiple Images
For articles or products with multiple preview images:
<script setup lang="ts">
useHead({
meta: [
{ property: 'og:image', content: 'https://example.com/image1.png' },
{ property: 'og:image', content: 'https://example.com/image2.png' },
{ property: 'og:image', content: 'https://example.com/image3.png' }
]
})
</script>
Platform Caching Behavior
Different social platforms handle Open Graph caching differently:
- Facebook: ~30 days cache; force refresh via Sharing Debugger
- LinkedIn: 7+ days aggressive caching; images must be publicly accessible
- Twitter/X: Respects OG tags but prioritizes Twitter Card tags
- Discord: Real-time previews with animated GIF support
- Slack: Weekly cache refresh
Always test your OG tags with platform debuggers after deployment.
Complete Example
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute()
const config = useRuntimeConfig()
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)
if (!post.value) {
throw createError({ statusCode: 404 })
}
const ogImageUrl = `${config.public.siteUrl}/api/og?title=${encodeURIComponent(post.value.title)}&author=${encodeURIComponent(post.value.author)}`
useSeoMeta({
title: post.value.title,
description: post.value.excerpt,
// Open Graph
ogTitle: post.value.title,
ogDescription: post.value.excerpt,
ogType: 'article',
ogUrl: `${config.public.siteUrl}${route.fullPath}`,
ogImage: ogImageUrl,
ogImageAlt: post.value.title,
ogImageWidth: '1200',
ogImageHeight: '630',
// Article metadata
articlePublishedTime: post.value.publishedAt,
articleModifiedTime: post.value.updatedAt,
articleAuthor: post.value.author,
articleSection: post.value.category,
articleTag: post.value.tags,
// Twitter Card
twitterCard: 'summary_large_image',
twitterTitle: post.value.title,
twitterDescription: post.value.excerpt,
twitterImage: ogImageUrl,
twitterImageAlt: post.value.title
})
</script>
Testing and Debugging
Verify Server-Side Rendering
Check your HTML source (View Page Source):
<meta property="og:title" content="Your Page Title">
<meta property="og:image" content="https://example.com/og-image.png">
If tags are missing, ensure SSR is enabled (not SPA mode) and tags are set in <script setup>.
Platform Debuggers
- Facebook: Sharing Debugger
- Twitter/X: Card Validator
- LinkedIn: Post Inspector
- OG Check: Multi-platform preview tool
Common Issues
| Problem | Solution |
|---|---|
| Image not showing | Verify absolute URL, check HTTPS, ensure public access |
| Wrong image appears | Clear platform cache with debugger tools |
| Title/description truncated | Reduce character count (title: 40-60, description: 120-160) |
| Tags not updating | Ensure SSR is enabled; avoid setting in onMounted() |
Pre-Launch Checklist
Before publishing:
-
ogTitle: 40-60 characters, compelling -
ogDescription: 120-160 characters, descriptive -
ogImage: Absolute URL, 1200x630px minimum, under 1MB -
ogImageAlt: Meaningful description provided -
ogUrl: Absolute path matching canonical URL -
ogType: Correct content type (website,article, etc.) -
twitterCard: Set tosummary_large_image - Twitter tags mirror Open Graph tags
- Tested with 2+ platform debuggers
- SSR verified (view HTML source)
Next Steps
Validate your Open Graph tags with OG Check to preview how they appear across Facebook, Twitter, LinkedIn, and Discord. Get instant visual feedback and copy-ready meta tag code.
For dynamic OG images, implement an API route that generates images on-the-fly using @vercel/og or satori.
References
- Nuxt Docs: SEO & Meta (useSeoMeta) — https://nuxt.com/docs/4.x/getting-started/seo-meta