---
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
::

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()`{lang="ts"}](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

| Spec             | Value                 |
| ---------------- | --------------------- |
| Minimum size     | 600x315px             |
| Recommended size | 1200x600px (2:1)      |
| Max file size    | 5MB (1MB recommended) |
| Formats          | JPG, PNG, WebP, GIF   |
| URL              | Absolute HTTPS        |

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`
::

### 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:

| Twitter Tag           | Fallback         |
| --------------------- | ---------------- |
| `twitter:title`       | `og:title`       |
| `twitter:description` | `og:description` |
| `twitter:image`       | `og:image`       |

`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](http://Schema.org)
- No redirect following for images: use direct URLs

## Platform Cache Times

| Platform  | Cache Duration                      | Clear Cache                                                      |
| --------- | ----------------------------------- | ---------------------------------------------------------------- |
| Facebook  | ~30 days                            | [Sharing Debugger](https://developers.facebook.com/tools/debug/) |
| LinkedIn  | ~7 days                             | [Post Inspector](https://www.linkedin.com/post-inspector/)       |
| X/Twitter | ~7 days (not officially documented) | Add `?v=2` to the URL                                            |
| Slack     | ~30 minutes                         | Add `?v=2` to the URL                                            |
| Discord   | Cached per message                  | Delete and re-paste the link                                     |
| Telegram  | Effectively indefinite              | [@WebpageBot](https://t.me/WebpageBot)                           |

::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.
::

## 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{#vue-social-sharing}
- Set `ogTitle`, `ogDescription`, `ogImage`, and `ogUrl` with `useSeoMeta()`{lang="ts"} 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()`{lang="ts"}) so crawlers see it
- Test previews with the Facebook Sharing Debugger, LinkedIn Post Inspector, or the [Social Share Debugger](/tools/social-share-debugger)
::

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)