---
title: "Social Sharing Meta Tags in Vue"
description: "Set Open Graph and Twitter Card tags in Vue so links preview correctly on Facebook, X, LinkedIn, Slack, and Discord instead of plain text."
canonical_url: "https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing"
last_updated: "2026-07-16"
---

<key-takeaways>

- OG tags control social previews on Facebook, [LinkedIn](https://linkedin.com), Slack, Discord
- Images must be absolute HTTPS URLs (not relative paths)
- Twitter/X requires `twitterCard`: there's no fallback from OG tags

</key-takeaways>

Social platforms use Open Graph (OG) and Twitter Card meta tags to generate link previews. Without them, shared links appear as plain text.

**SSR required**: Social crawlers don't run JavaScript. Your meta tags must be in the initial HTML response, which means [server-side rendering](/learn-seo/vue/routes-and-rendering/rendering) or prerendering.

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  // Open Graph (Facebook, LinkedIn, Discord, Slack)
  ogTitle: 'Your page title',
  ogDescription: 'Short description',
  ogImage: 'https://mysite.com/og.png',
  ogUrl: 'https://mysite.com/page',
  // Twitter/X (required - no OG fallback for card type)
  twitterCard: 'summary_large_image'
})
</script>
```

## Quick Tips

1. **Images matter**: a page with a working `og:image` renders as a card with a preview image; a page without one renders as a bare text link. Images must be absolute URLs.
2. **Social titles can differ**: your `og:title` doesn't have to match your [page title](/learn-seo/vue/mastering-meta/titles). Search intent doesn't apply on social, so casual or provocative language works fine.
3. **Always set twitterCard**: X/Twitter has no fallback for card type. Skip it and you get plain text.

## Open Graph Tags

Use [`useSeoMeta()`](https://unhead.unjs.io/docs/head/api/composables/use-seo-meta) for OG tags: it handles the `property` attribute automatically.

### Core Tags

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  ogTitle: 'Hey! Open graph images are important.',
  ogDescription: 'But who reads the description anyway?',
  ogImage: 'https://mysite.com/og.png', // must be absolute URL
  ogUrl: 'https://mysite.com/products/item', // canonical URL
  ogType: 'website' // or 'article', 'product', etc.
})
</script>
```

### Image Requirements

<table>
<thead>
  <tr>
    <th>
      Spec
    </th>
    
    <th>
      Value
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Minimum size
    </td>
    
    <td>
      600x315px
    </td>
  </tr>
  
  <tr>
    <td>
      Recommended size
    </td>
    
    <td>
      1200x600px (2:1)
    </td>
  </tr>
  
  <tr>
    <td>
      Max file size
    </td>
    
    <td>
      5MB (1MB recommended)
    </td>
  </tr>
  
  <tr>
    <td>
      Formats
    </td>
    
    <td>
      JPG, PNG, WebP, GIF
    </td>
  </tr>
  
  <tr>
    <td>
      URL
    </td>
    
    <td>
      Absolute HTTPS
    </td>
  </tr>
</tbody>
</table>

Use 1200x600px (2:1): it matches X's card ratio, renders cleanly on Discord and Slack, and Facebook displays it fine even though its own docs still list the older 1.91:1 (1200x630px) spec. One image covers every platform.

Include dimensions for faster rendering:

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  ogImage: 'https://mysite.com/og-images/preview.jpg',
  ogImageAlt: 'Product preview showing 3 stacked boxes',
  ogImageWidth: 1200,
  ogImageHeight: 600,
  ogImageType: 'image/jpeg'
})
</script>
```

<warning>

OG images must use absolute URLs with HTTPS. Relative paths like `/images/og.png` won't work: social crawlers don't know your domain. Always include the full URL, such as `https://mysite.com/images/og.png`

</warning>

### Article Metadata

For blog posts, add article-specific tags:

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  ogType: 'article',
  articleAuthor: ['Harlan Wilton'],
  articleSection: 'SEO Tutorials',
  articleTag: ['vue', 'seo', 'meta-tags'],
  articlePublishedTime: '2024-11-05T00:00:00Z',
  articleModifiedTime: '2026-07-16T00:00:00Z'
})
</script>
```

## Twitter/X Cards

X requires its own `twitter:*` meta tags. Without `twitter:card`, your links appear as plain text: no image, no description.

### Card Types

**summary_large_image**: Full-width image. Use this for most pages.

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  twitterCard: 'summary_large_image',
  twitterImage: '/preview.jpg' // 2:1 ratio, min 300x157px
})
</script>
```

**summary**: Small square thumbnail. For compact previews.

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  twitterCard: 'summary',
  twitterImage: '/icon.jpg' // 1:1 ratio, min 144x144px
})
</script>
```

**player** and **app** cards require [approval from X](https://developer.x.com/en/docs/x-for-websites/cards/overview/player-card).

### X Fallbacks

X [falls back to Open Graph](https://developer.x.com/en/docs/x-for-websites/cards/overview/markup) when `twitter:*` equivalents are missing:

<table>
<thead>
  <tr>
    <th>
      Twitter Tag
    </th>
    
    <th>
      Fallback
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        twitter:title
      </code>
    </td>
    
    <td>
      <code>
        og:title
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        twitter:description
      </code>
    </td>
    
    <td>
      <code>
        og:description
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        twitter:image
      </code>
    </td>
    
    <td>
      <code>
        og:image
      </code>
    </td>
  </tr>
</tbody>
</table>

`twitter:card` has **no fallback**: skip it and you get no card.

Minimal setup using fallbacks:

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  twitterCard: 'summary_large_image', // required
  ogTitle: 'My page',
  ogImage: 'https://mysite.com/image.jpg'
})
</script>
```

### Optional Twitter Tags

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  twitterSite: '@yourhandle', // Site's X handle
  twitterCreator: '@authorhandle', // Author's handle
  twitterImageAlt: 'Alt text' // 420 chars max
})
</script>
```

## Slack & Discord

Both platforms read Open Graph tags directly, no platform-specific tags needed.

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  ogTitle: 'Dashboard Analytics',
  ogDescription: '847 active users, 12.4% conversion rate',
  ogImage: 'https://mysite.com/og/dashboard.png'
})
</script>
```

Slack also reads Twitter Card tags as fallback. Discord supports animated GIFs, but it caches embeds per message rather than fetching live on every view.

Slack-specific behavior:

- Cache: about 30 minutes per URL
- To refresh: add `?v=2` to the URL
- Ignores: `og:type`, `og:locale`, `article:*` tags, Schema.org
- No redirect following for images: use direct URLs

## Platform Cache Times

<table>
<thead>
  <tr>
    <th>
      Platform
    </th>
    
    <th>
      Cache Duration
    </th>
    
    <th>
      Clear Cache
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Facebook
    </td>
    
    <td>
      ~30 days
    </td>
    
    <td>
      <a href="https://developers.facebook.com/tools/debug/" rel="nofollow">
        Sharing Debugger
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      LinkedIn
    </td>
    
    <td>
      ~7 days
    </td>
    
    <td>
      <a href="https://www.linkedin.com/post-inspector/" rel="nofollow">
        Post Inspector
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      X/Twitter
    </td>
    
    <td>
      ~7 days (not officially documented)
    </td>
    
    <td>
      Add <code>
        ?v=2
      </code>
      
       to the URL
    </td>
  </tr>
  
  <tr>
    <td>
      Slack
    </td>
    
    <td>
      ~30 minutes
    </td>
    
    <td>
      Add <code>
        ?v=2
      </code>
      
       to the URL
    </td>
  </tr>
  
  <tr>
    <td>
      Discord
    </td>
    
    <td>
      Cached per message
    </td>
    
    <td>
      Delete and re-paste the link
    </td>
  </tr>
  
  <tr>
    <td>
      Telegram
    </td>
    
    <td>
      Effectively indefinite
    </td>
    
    <td>
      <a href="https://t.me/WebpageBot" rel="nofollow">
        @WebpageBot
      </a>
    </td>
  </tr>
</tbody>
</table>

<note icon="i-simple-icons-telegram">

Telegram's OG cache is effectively infinite. While it may expire for links no one has interacted with in a long time, you should assume it never clears on its own. The official [@WebpageBot](https://t.me/WebpageBot) can instantly refresh the OG image and metadata for everyone.

</note>

## Dynamic Meta Tags

Data must be available during server render. `onMounted` runs too late for crawlers.

```ts
// entry-server.ts
export async function render(url: string) {
  const product = await fetch(`https://api.mysite.com/product/${url}`).then(r => r.json())
  const app = createSSRApp(App)
  app.provide('product', product)
  return { app, product }
}
```

```vue
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'
import { inject } from 'vue'

const product = inject <Product> ('product')

useSeoMeta({
  ogTitle: product?.name,
  ogDescription: product?.shortPitch,
  ogImage: `https://mysite.com/api/og?title=${encodeURIComponent(product?.name || '')}`,
  twitterCard: 'summary_large_image'
})
</script>
```

## Testing & Troubleshooting

Test previews before you ship:

1. [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/): Also validates OG tags for Slack
2. [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/)
3. X/Twitter: Compose a tweet (don't post) to see preview
4. Discord: Paste link for real-time preview

**Image not showing**

- URL must be absolute HTTPS (not relative)
- File under 5MB
- Image returns 200 (not redirect)
- Not blocked by robots.txt

**Wrong title/description**

- Platform cached the old version: add `?v=2` to the URL
- For X: check you're setting `twitterCard`

**Card not appearing at all**

- Missing `twitterCard` for X (no fallback)
- Page requires auth (crawlers can't log in)

## OG Image Tools

Creating OG images manually is tedious:

- [OG Image Playground](https://og-playground.vercel.app/): Design and export with code
- [x-satori](https://github.com/Zhengqbbb/x-satori): Vue SFCs + Tailwind to generate at build time

## Checklist

<checklist id="vue-social-sharing">

- Set `ogTitle`, `ogDescription`, `ogImage`, and `ogUrl` with `useSeoMeta()` from `@unhead/vue`
- Use an absolute HTTPS URL for `ogImage`, at least 600x315px
- Set `twitterCard` too: there's no OG fallback for the card type itself
- Fetch data before render (not in `onMounted()`) so crawlers see it
- Test previews with the Facebook Sharing Debugger, LinkedIn Post Inspector, or the [Social Share Debugger](/tools/social-share-debugger)

</checklist>

Using Nuxt? [Nuxt SEO](/docs/nuxt-seo/getting-started/introduction) handles social sharing automatically with the OG Image module. [Learn more about Social Sharing in Nuxt →](/learn-seo/nuxt/mastering-meta/open-graph)
