Vue.js Open Graph Meta Tags: How to Add OG Tags Correctly

Learn how to add og:title, og:image, og:description, and Twitter card tags to Vue.js apps — covering Vue Router, Nuxt 3, and vue-meta.

Why Vue.js OG tags need special handling

Vue.js is a client-side framework by default. When a social crawler (Twitter, LinkedIn, Discord) fetches your URL, it does not execute JavaScript — it reads the raw HTML returned by the server. A single-page Vue app that injects <meta> tags via JavaScript will appear blank to crawlers, resulting in missing previews.

The fix is server-side rendering (SSR) or static site generation (SSG). The two most common paths are Nuxt 3 (Vue's meta-framework with built-in SSR) and Vite SSG (static generation for simpler apps).

Option 1: Nuxt 3 (recommended)

Nuxt 3 ships with a useHead composable and the useSeoMeta helper for type-safe meta management. Both render server-side automatically.

<!-- pages/index.vue -->
<script setup>
useSeoMeta({
  title: 'My Page Title',
  description: 'My page description.',
  ogTitle: 'My Page Title',
  ogDescription: 'My page description.',
  ogImage: 'https://example.com/og.jpg',
  ogUrl: 'https://example.com',
  ogType: 'website',
  twitterCard: 'summary_large_image',
  twitterTitle: 'My Page Title',
  twitterDescription: 'My page description.',
  twitterImage: 'https://example.com/og.jpg',
})
</script>

For dynamic pages (e.g., blog posts fetched from an API), use useSeoMeta inside useAsyncData:

<script setup>
const route = useRoute()
const { data: post } = await useAsyncData(() =>
  $fetch(`/api/posts/${route.params.slug}`)
)

useSeoMeta({
  title: post.value?.title,
  ogTitle: post.value?.title,
  ogDescription: post.value?.excerpt,
  ogImage: post.value?.coverImage,
  ogUrl: `https://example.com/posts/${route.params.slug}`,
  twitterCard: 'summary_large_image',
})
</script>

Option 2: Vue 3 SPA with vue-meta

If you're building a pure SPA without SSR, you can use @unhead/vue (successor to vue-meta) with a prerender plugin. Without prerendering, social crawlers will still see empty tags.

// main.ts
import { createApp } from 'vue'
import { createHead } from '@unhead/vue'
import App from './App.vue'

const app = createApp(App)
const head = createHead()
app.use(head)
app.mount('#app')

// MyComponent.vue
<script setup>
import { useHead } from '@unhead/vue'

useHead({
  title: 'My Page',
  meta: [
    { property: 'og:title', content: 'My Page' },
    { property: 'og:description', content: 'Description here.' },
    { property: 'og:image', content: 'https://example.com/og.jpg' },
    { name: 'twitter:card', content: 'summary_large_image' },
  ],
})
</script>

Pair this with vite-plugin-ssr or vite-ssg to generate static HTML at build time.

Required OG tags checklist

  • og:title — page title (up to ~60 chars)
  • og:description — summary (up to ~155 chars)
  • og:image — absolute HTTPS URL, 1200×630 px
  • og:url — canonical URL for the page
  • og:type — "website" or "article"
  • twitter:card — "summary_large_image" for full-width cards

Common Vue OG tag mistakes

  • Client-only rendering: tags injected via JavaScript are invisible to crawlers. Use Nuxt SSR or a prerender plugin.
  • Relative image URLs: og:image must be an absolute URL (https://...).
  • Wrong image dimensions: aim for 1200×630 px. Images under 200px wide are ignored by some platforms.
  • Stale cache: after fixing OG tags, use each platform's debugger to force a re-scrape.

Verify your Vue OG tags

After deploying, test your OG tags across every platform where your links will be shared:

Check your Vue.js OG tags instantly → Paste your URL into OGFixer to see exactly how your link appears on Twitter, LinkedIn, Discord, and Slack before you share it.