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 pxog:url— canonical URL for the pageog: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:
- Twitter/X Card Validator
- LinkedIn Post Inspector
- Facebook Sharing Debugger
- OGFixer — preview on Twitter, Slack, Discord simultaneously
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.