How to Add Dynamic OG Images to Nuxt.js with OGPeek API
Nuxt.js gives you SSR, static generation, and hybrid rendering out of the box. But generating unique Open Graph images for every page still means wrestling with server middleware, image libraries, or manual exports from Figma. Here is how to add dynamic, branded social cards to any Nuxt app in under five minutes—using a single API URL, zero npm packages, and Nuxt's built-in useHead and useSeoMeta composables.
Why Nuxt.js Needs Dynamic OG Images
When someone shares a URL from your Nuxt app on Twitter/X, LinkedIn, Slack, or Discord, the platform's crawler fetches the HTML, reads the og:image meta tag, and requests that image to display as a preview card. If every page points to the same static image—or worse, has no og:image at all—your links blend into the feed and get fewer clicks.
This matters because social preview cards are the first impression of your content. A blog post titled "Building a Multi-Tenant SaaS with Nuxt 3" deserves a social card that says exactly that—not a generic logo or a broken image placeholder.
The traditional approaches each introduce friction that Nuxt developers should not have to deal with:
- Manual creation in Figma or Canva — Works for 5 pages. Completely unmanageable at 50. Impossible at 500.
- nuxt-og-image module — Renders images on your server using Satori. Requires server-side rendering, adds build complexity, and needs careful configuration for fonts and layouts. If you are using
nuxt generatefor a static site, the prerender step adds significant build time. - Nuxt Image module — Great for optimizing content images, but not designed for generating OG social cards with dynamic text overlays.
- Custom server middleware with Sharp or Puppeteer — You write a Nitro server route that spins up a headless browser or processes images on every request. Heavy dependencies, high server load, and failure modes to monitor.
There is a simpler approach: point your og:image meta tag at an external API that returns a PNG. Your Nuxt app stays lean. The image generation happens somewhere else entirely.
How OGPeek Works
OGPeek is an OG image generation API. You construct a URL with your title, subtitle, template, and brand color as query parameters. When that URL is requested, OGPeek renders the image and returns a 1200×630 PNG—the standard size for Open Graph images across all major platforms.
GET https://ogpeek.com/api/v1/og
?title=Your+Page+Title
&subtitle=yourdomain.com
&template=gradient
&theme=midnight
&brandColor=%23FF7A00
That is the entire integration. A GET request returns a PNG. No API keys for the free tier, no SDK to install, no Nuxt module to configure. You embed this URL in your page's meta tags using useHead or useSeoMeta and every page gets a unique social card based on its title.
Key point: Social platform crawlers follow the URL in your og:image meta tag and fetch whatever is there. They cannot distinguish between a static PNG on your CDN and an API endpoint that generates a PNG on the fly. Both are just HTTP responses with Content-Type: image/png.
Step 1: Set Global Defaults in nuxt.config.ts
Nuxt's app.head configuration in nuxt.config.ts lets you define default meta tags that apply to every page in your application. This is the ideal place to set fallback OG tags for pages that do not define their own.
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
title: 'Your Site Name',
meta: [
{ name: 'description', content: 'Your site description.' },
{ property: 'og:site_name', content: 'Your Site Name' },
{ property: 'og:type', content: 'website' },
{
property: 'og:image',
content: 'https://ogpeek.com/api/v1/og'
+ '?title=Your+Site+Name'
+ '&subtitle=yourdomain.com'
+ '&template=gradient'
+ '&theme=midnight'
+ '&brandColor=%23FF7A00'
},
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' }
]
}
}
})
This gives your site a branded default OG image. Any page that does not explicitly set its own og:image will fall back to this one. Pages that call useHead or useSeoMeta with their own OG tags will override these defaults automatically—Nuxt handles the merge for you.
Step 2: Dynamic OG Images with useHead
Nuxt 3 provides the useHead composable for setting head elements reactively inside any component or page. This is the most direct way to add a dynamic OG image to an individual page.
For a static page like an About page, the integration looks like this:
<!-- pages/about.vue -->
<script setup>
const title = 'About Us'
const description = 'Learn about our team and mission.'
const ogImage = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(title)
}&subtitle=${
encodeURIComponent('yourdomain.com')
}&template=gradient&theme=midnight&brandColor=%23FF7A00`
useHead({
title,
meta: [
{ name: 'description', content: description },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ property: 'og:image', content: ogImage },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: title },
{ name: 'twitter:image', content: ogImage }
]
})
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
</template>
That is the complete integration for a single page. The og:image meta tag points to OGPeek, which generates a 1200×630 PNG with "About Us" as the title text. Zero dependencies added to your project.
For dynamic routes like a blog at /blog/[slug], use useAsyncData or useFetch to load your content and then pass the data into useHead:
<!-- pages/blog/[slug].vue -->
<script setup>
const route = useRoute()
const { data: post } = await useFetch(
`/api/posts/${route.params.slug}`
)
const ogImage = computed(() =>
`https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(post.value?.title || '')
}&subtitle=${
encodeURIComponent('Blog · yourdomain.com')
}&template=gradient&theme=midnight&brandColor=%23FF7A00`
)
useHead({
title: () => `${post.value?.title} — Your Site`,
meta: [
{ name: 'description', content: () => post.value?.excerpt },
{ property: 'og:title', content: () => post.value?.title },
{ property: 'og:description', content: () => post.value?.excerpt },
{ property: 'og:type', content: 'article' },
{ property: 'og:image', content: () => ogImage.value },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: () => post.value?.title },
{ name: 'twitter:image', content: () => ogImage.value }
]
})
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<p>{{ post.excerpt }}</p>
<div v-html="post.body" />
</article>
</template>
Every blog post now gets a branded social card with its own title. A post called "Building Multi-Tenant SaaS with Nuxt 3" produces a social card with that exact text. No manual image creation, no server-side rendering of images on your infrastructure.
Tip: Notice the use of computed() for the OG image URL and arrow functions for reactive meta values. This ensures the meta tags update reactively when the fetched data resolves. Nuxt's useHead supports reactive values natively.
Step 3: Cleaner Syntax with useSeoMeta
Nuxt 3 also provides useSeoMeta—a composable specifically designed for SEO meta tags. Instead of manually specifying property and content for each tag, you pass a flat object with typed keys. This is the cleanest way to handle OG tags in Nuxt.
<!-- pages/pricing.vue -->
<script setup>
const title = 'Pricing'
const description = 'Simple, transparent pricing for teams of all sizes.'
const ogImage = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(title)
}&subtitle=${
encodeURIComponent('yourdomain.com')
}&template=gradient&theme=midnight&brandColor=%23FF7A00`
useSeoMeta({
title,
description,
ogTitle: title,
ogDescription: description,
ogImage: ogImage,
ogImageWidth: 1200,
ogImageHeight: 630,
twitterCard: 'summary_large_image',
twitterTitle: title,
twitterImage: ogImage
})
</script>
<template>
<div>
<h1>Pricing</h1>
<!-- pricing content -->
</div>
</template>
The useSeoMeta composable produces identical HTML output to useHead with meta arrays, but the code is shorter and easier to read. The keys like ogTitle, ogImage, and twitterCard are fully typed—your IDE will autocomplete them and catch typos at development time.
For dynamic pages, use useServerSeoMeta to ensure the tags are rendered during SSR (important for social crawlers that do not execute JavaScript):
<script setup>
const { data: post } = await useFetch(`/api/posts/${useRoute().params.slug}`)
useServerSeoMeta({
title: () => post.value?.title,
description: () => post.value?.excerpt,
ogTitle: () => post.value?.title,
ogDescription: () => post.value?.excerpt,
ogImage: () => `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(post.value?.title || '')
}&subtitle=${
encodeURIComponent('yourdomain.com')
}&template=gradient&theme=midnight&brandColor=%23FF7A00`,
ogImageWidth: 1200,
ogImageHeight: 630,
twitterCard: 'summary_large_image',
twitterTitle: () => post.value?.title,
twitterImage: () => `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(post.value?.title || '')
}&template=gradient&theme=midnight&brandColor=%23FF7A00`
})
</script>
Reusable Composable Pattern: useOGImage
If your Nuxt app has many pages—a blog, a documentation site, product pages, landing pages—you will end up repeating the same OGPeek URL construction logic everywhere. The solution is to extract it into a reusable composable.
Create a useOGImage composable in your composables/ directory (Nuxt auto-imports these):
// composables/useOGImage.ts
interface OGImageOptions {
title: string
description?: string
subtitle?: string
template?: 'basic' | 'gradient' | 'split' | 'hero'
theme?: 'dark' | 'light' | 'midnight' | 'forest' | 'sunset'
brandColor?: string
}
export function useOGImage(options: OGImageOptions) {
const {
title,
description = '',
subtitle = 'yourdomain.com',
template = 'gradient',
theme = 'midnight',
brandColor = '#FF7A00'
} = options
const ogImageUrl = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(title)
}&subtitle=${
encodeURIComponent(subtitle)
}&template=${template}&theme=${theme}&brandColor=${
encodeURIComponent(brandColor)
}`
useSeoMeta({
title,
description,
ogTitle: title,
ogDescription: description,
ogImage: ogImageUrl,
ogImageWidth: 1200,
ogImageHeight: 630,
twitterCard: 'summary_large_image',
twitterTitle: title,
twitterImage: ogImageUrl
})
return { ogImageUrl }
}
Now every page in your Nuxt app can set its OG image with a single function call:
<!-- pages/features.vue -->
<script setup>
useOGImage({
title: 'Features',
description: 'Everything you need to build modern web apps.',
subtitle: 'yourdomain.com'
})
</script>
<template>
<div>
<h1>Features</h1>
</div>
</template>
One line. One composable. Every page gets a unique, branded social card. The composable handles URL construction, encoding, and all the meta tags. If you change your brand color or template, you update one file and every page reflects the change.
For dynamic pages, pass reactive data into the composable using a watch or compute the values before calling it:
<!-- pages/docs/[section].vue -->
<script setup>
const route = useRoute()
const { data: section } = await useFetch(
`/api/docs/${route.params.section}`
)
useOGImage({
title: section.value?.title || 'Documentation',
description: section.value?.summary || '',
subtitle: 'Docs · yourdomain.com'
})
</script>
OGPeek vs. Nuxt Image Module vs. nuxt-og-image vs. Manual
Here is how the main approaches compare for Nuxt.js projects that need dynamic social cards:
| Feature | OGPeek API | Nuxt Image Module | nuxt-og-image | Manual (Figma) |
|---|---|---|---|---|
| Setup time | 2 minutes | N/A (not for OG) | 15–45 minutes | 5–10 min per image |
| Dependencies added | 0 | @nuxt/image | nuxt-og-image + Satori | 0 |
| Dynamic text on images | Yes (via URL params) | No | Yes (Vue components) | No |
| Server load | Zero (external API) | Image optimization only | Renders on your server | Zero |
| Works with nuxt generate (SSG) | Yes | Yes | Yes (prerender step) | Yes |
| Build time impact | None | Minimal | Adds prerender time | None |
| Custom CSS layout | Pre-built templates | N/A | Full control (Vue) | Full control |
| Hosting lock-in | None | None | Needs SSR or prerender | None |
| Scales to 1000+ pages | Yes | N/A | Depends on build/server | No |
| Maintenance burden | None | Module updates | Module + Satori updates | Per-page manual effort |
The Nuxt Image module (@nuxt/image) is excellent for optimizing and resizing content images, but it is not designed to generate OG social cards with dynamic text. It solves a different problem entirely.
The nuxt-og-image module is the most feature-rich option if you need pixel-perfect custom layouts defined as Vue components. However, it requires Satori, adds build complexity, and renders images on your server or during the prerender step. For most projects that need branded social cards with a title, subtitle, and accent color, OGPeek delivers the same result with zero configuration.
Customizing Your OG Images
OGPeek supports several parameters to match your brand:
- template —
basic,gradient,split, orhero. The gradient template works well for most developer sites. - theme —
dark,light,midnight,forest,sunset. Choose one that matches your Nuxt app's design system. - brandColor — Any hex color, URL-encoded (e.g.,
%2300DC82for Nuxt's green). Used as an accent in the generated image. - subtitle — Your domain, tagline, or section name. Appears below the title for additional context.
For a Nuxt project, you might use Nuxt's signature green as the brand color:
const ogImage = `https://ogpeek.com/api/v1/og?title=${
encodeURIComponent(title)
}&subtitle=${
encodeURIComponent('Built with Nuxt · yourdomain.com')
}&template=gradient&theme=dark&brandColor=%2300DC82`
Testing Your OG Images
After deploying your Nuxt app, verify your OG images work correctly:
- View the page source and confirm the
og:imagemeta tag contains a valid OGPeek URL with your page title encoded in it. In Nuxt SSR mode, the meta tags are rendered in the initial HTML response. - Open the OGPeek URL directly in your browser. You should see a 1200×630 PNG with your title rendered on it.
- Use a debugger tool like the Twitter Card Validator or Facebook Sharing Debugger to see exactly what the platform will display.
- Share a link in a private Slack channel or Discord server to see the preview card in a real context.
If the image does not appear, check that encodeURIComponent() is encoding special characters correctly. Ampersands, quotes, and hash symbols in titles can break the URL if left unencoded. Also verify that your Nuxt app renders the meta tags server-side—most social crawlers do not execute JavaScript.
SSR is critical: Social platform crawlers (Facebook, Twitter/X, LinkedIn, Slack) do not run JavaScript. If you are using Nuxt in SPA mode, your OG meta tags will not be visible to crawlers. Use SSR (ssr: true in nuxt.config.ts, which is the default) or prerender your pages with nuxt generate to ensure crawlers can read the tags.
Performance Considerations
OGPeek caches generated images at the CDN edge. The first request for a given URL renders the image and caches it. Subsequent requests—including from different social platforms sharing the same link—are served from the cache with sub-50ms response times.
Since the og:image URL is only fetched by social platform crawlers (not by your site's actual visitors in their browsers), there is zero impact on your Nuxt app's performance metrics. Core Web Vitals, Lighthouse scores, and page load times are completely unaffected. Nuxt's built-in performance optimizations—code splitting, lazy hydration, payload extraction—remain untouched.
Frequently Asked Questions
Does OGPeek work with Nuxt.js static site generation (nuxt generate)?
Yes. OGPeek works perfectly with nuxt generate and SSG mode. The og:image meta tag contains a URL to OGPeek's API, so the image is generated externally when a social platform requests it. Your Nuxt site can be fully prerendered to static HTML and hosted anywhere—Netlify, Cloudflare Pages, Firebase Hosting, or any static file server.
Do I need to install any npm packages to use OGPeek with Nuxt?
No. OGPeek requires zero npm packages. You use Nuxt's built-in useHead or useSeoMeta composables to add an og:image meta tag pointing to an OGPeek URL. There is nothing to install, configure, or maintain in your project dependencies.
How is OGPeek different from the nuxt-og-image module?
The nuxt-og-image module renders OG images on your own server using Satori and requires server-side rendering or a prerender step. It adds build complexity, configuration files, and a dependency on Satori's CSS subset. OGPeek is an external API—you pass a URL with your title as a query parameter and get a PNG back. No server-side rendering of images, no build step, no module configuration.
Can I use useHead and useSeoMeta together for OG images in Nuxt?
Yes. Nuxt's useHead and useSeoMeta composables can be used together in the same component. useSeoMeta provides a cleaner, flattened API specifically for SEO meta tags, while useHead handles the full range of head elements including title, link, and script tags. Both support reactivity and will merge correctly.
What image size does OGPeek return for Nuxt social cards?
OGPeek returns 1200×630 PNG images by default, which is the recommended size for Open Graph images across Facebook, LinkedIn, Twitter/X, Slack, Discord, and other platforms that render link previews. This size is standard regardless of which framework you use.
Start generating OG images for your Nuxt.js app
Read the full API documentation, explore templates, and grab your integration URL. Free tier available—no signup required.
Get started free → Read the API docsConclusion
Nuxt.js makes building modern web applications straightforward with its file-based routing, auto-imports, and flexible rendering modes. Adding dynamic OG images should be equally straightforward—and with OGPeek, it is.
Set global defaults in nuxt.config.ts. Override per-page with useHead or useSeoMeta. Extract the pattern into a useOGImage composable so every page gets a branded social card in a single line. No npm packages. No headless browsers. No server-side image rendering on your infrastructure.
Whether you deploy with SSR, SSG, or hybrid rendering, OGPeek works the same way: a GET request returns a PNG. Your Nuxt app stays lean. Your social cards stay dynamic.