Twitter ignores most Open Graph tags and requires its own twitter:* meta tags for rich link previews. Without them, your links appear as plain text with no image or description.
<script setup lang="ts">
useSeoMeta({
twitterCard: 'summary_large_image',
twitterImage: '/social-preview.jpg',
twitterTitle: 'Your page title',
twitterDescription: 'Your page description'
})
</script>
Twitter supports four card types via twitter:card:
summary_large_image - Full-width image preview (1200x630px). Use this for most pages.
useSeoMeta({
twitterCard: 'summary_large_image',
twitterImage: '/preview.jpg' // Minimum 300x157px, aspect ratio 2:1
})
summary - Small square thumbnail (1:1 aspect ratio). Don't use this unless you specifically want a smaller preview.
useSeoMeta({
twitterCard: 'summary',
twitterImage: '/icon.jpg' // Minimum 144x144px
})
player - Embedded video/audio player. Requires approval from Twitter.
app - Mobile app install prompts. Requires app store URLs and IDs.
Most sites should only use summary_large_image.
These three tags are mandatory for any Twitter Card:
useSeoMeta({
twitterCard: 'summary_large_image',
twitterTitle: 'Page title - 70 characters max',
twitterImage: 'https://example.com/image.jpg' // Must be absolute URL
})
Image requirements:
summary_large_image: 2:1 ratio (1200x630px recommended)summary: 1:1 ratio (300x300px minimum)useSeoMeta({
twitterCard: 'summary_large_image',
twitterTitle: 'Your title',
twitterImage: '/preview.jpg',
twitterDescription: 'Description text - 200 characters max',
twitterSite: '@yourhandle', // Your site's Twitter handle
twitterCreator: '@authorhandle' // Content author's handle
})
twitterDescription defaults to og:description if not set, but specify it to control the exact text Twitter shows.
Twitter falls back to Open Graph tags when twitter:* tags are missing:
| Twitter Tag | Fallback |
|---|---|
twitter:title | og:title |
twitter:description | og:description |
twitter:image | og:image |
This won't work:
// ❌ Twitter will show nothing
useSeoMeta({
ogTitle: 'My page',
ogImage: '/image.jpg'
})
This works:
// ✅ Twitter uses og:* tags as fallback
useSeoMeta({
twitterCard: 'summary_large_image', // Required - no fallback
ogTitle: 'My page',
ogImage: '/image.jpg'
})
Set both to avoid relying on fallback behavior:
// ✅ Explicit control
useSeoMeta({
twitterCard: 'summary_large_image',
twitterTitle: 'Twitter-specific title (70 chars)',
twitterImage: '/twitter-preview.jpg',
ogTitle: 'OpenGraph title can be longer',
ogImage: '/og-preview.jpg'
})
Use Twitter Card Validator to preview your cards before sharing.
The validator caches results. If you update tags and don't see changes:
?v=2Common issues:
Image not showing - Check that:
Wrong title/description - Twitter cached the old version. Add ?v=1 to the URL.
Card not appearing - You forgot twitterCard meta tag. It has no fallback.
Set different cards for different pages:
<!-- ~/pages/blog/[slug].vue -->
<script setup lang="ts">
const { data: post } = await useAsyncData('post', () =>
queryContent('blog', route.params.slug).findOne()
)
useSeoMeta({
twitterCard: 'summary_large_image',
twitterTitle: post.value.title,
twitterDescription: post.value.excerpt,
twitterImage: post.value.coverImage
})
</script>
✅ Blog posts with feature images ✅ Product pages with product photos ✅ Landing pages with marketing visuals ✅ Documentation with branded images
❌ Pages with no images (just omit twitter:image) ❌ Internal tools not meant for sharing ❌ Password-protected content (Twitter can't fetch it)