How to Generate Open Graph Images in Nuxt
Generate dynamic Nuxt Open Graph images with nuxt-og-image, configure renderers and caching, validate previews, and automate screenshots when needed.
Direct answer: install nuxt-og-image, create a Vue component for your card, and expose its generated URL through the page’s og:image metadata. The module also supports screenshots of rendered pages when your design depends on browser layout. It lists Satori, Takumi, and browser prerendering as rendering choices.
What you are building
An Open Graph image is a publicly reachable image URL placed in your page metadata:
<meta property="og:image" content="https://example.com/__og-image__/article/my-post.png">
Social crawlers fetch that URL and display the result when someone shares the page. The image generator and the metadata setup are separate concerns: generating a card does not automatically prove that your SEO integration emits the right tag, so inspect the final HTML.
1. Check Nuxt and install the module
The maintainer source declares compatibility with Nuxt >=3.16.0. Check the version used by your project before copying version-specific configuration.
npx nuxi@latest module add og-image
This command adds the module to your Nuxt configuration and installs its package. Commit the resulting dependency changes, then restart the development server.
2. Choose a template or a page screenshot
Template-driven cards
Use a Vue component when every card follows the same structure: title, author, category, logo treatment, and optional image. This approach keeps typography and spacing explicit and makes route data easy to pass into the card.
Page screenshots
Use a screenshot when the visual must match an already rendered page, including browser-only layout, complex CSS, or components that are difficult to reproduce in a card template. Browser rendering adds deployment and runtime work, so it is usually a deliberate choice rather than a requirement for ordinary social cards.
3. Create a reusable OG component
Create a component such as components/OgCard.vue. Keep dimensions and contrast suitable for social previews, and make long titles wrap predictably.
<script setup lang="ts">
const props = defineProps<{
title: string
description?: string
category?: string
}>()
</script>
<template>
<div class="card">
<div class="brand">Your site</div>
<div class="category">{{ props.category }}</div>
<h1>{{ props.title }}</h1>
<p v-if="props.description">{{ props.description }}</p>
</div>
</template>
<style>
.card {
width: 1200px;
height: 600px;
box-sizing: border-box;
padding: 72px;
display: flex;
flex-direction: column;
justify-content: center;
background: #111827;
color: white;
font-family: Inter, Arial, sans-serif;
}
.brand { color: #a7f3d0; font-size: 28px; }
.category { margin-top: 48px; color: #93c5fd; font-size: 24px; }
h1 { max-width: 1000px; margin: 18px 0; font-size: 72px; line-height: 1.05; }
p { max-width: 900px; font-size: 30px; line-height: 1.3; color: #d1d5db; }
</style>
Use the module’s documented conventions to connect this component to your route data. The module listing describes generation from Vue components and screenshots of pages; exact component names and options can change between releases, so follow the documentation for the installed version.
4. Add metadata to each page
Generate a stable image URL from the route’s identifier and set it in Nuxt SEO metadata. The important result is the rendered HTML, not the particular helper you use.
<script setup lang="ts">
const route = useRoute()
const article = await $fetch(`/api/articles/${route.params.slug}`)
const ogImage = `https://example.com/__og-image__/article/${article.slug}.png`
useSeoMeta({
title: article.title,
ogTitle: article.title,
description: article.description,
ogDescription: article.description,
ogImage,
twitterCard: 'summary_large_image'
})
</script>
Use an absolute HTTPS URL that crawlers can reach without authentication. After building the page, view its source or fetch it with curl and confirm that exactly the intended og:image value is present.
5. Select a renderer
| Choice | Use it when | Trade-off |
|---|---|---|
| Satori | Your card uses supported HTML/CSS and needs a predictable generated image. | Some browser CSS and font behavior may not be available. |
| Takumi | Your templates fit its supported rendering model. | Verify compatibility with your exact component and deployment. |
| Browser prerendering | The design depends on real browser layout, page components, or complex CSS. | Requires browser-capable deployment and can use more runtime resources. |
No renderer is universally best. Compare template compatibility, fonts, runtime limits, and whether images are produced during prerendering or on demand.
6. Configure size, format, and caching
The module source documents defaults of 1200 × 600 pixels, PNG output, and a three-day cache maximum age. These are package defaults, not universal requirements from social platforms. Change them when your design or delivery constraints require it, and recheck the current release documentation before relying on a default.
- Keep a consistent aspect ratio across cards so previews do not jump between routes.
- Cache by a content version or slug so a changed title cannot keep serving an old card indefinitely.
- Use runtime cache storage that is shared by instances when your deployment scales horizontally.
- If cards are generated at build time, ensure every content route is included in the prerender process.
The source distinguishes runtime cache configuration from build-time settings and supports Nitro’s default cache storage, disabling runtime caching, or configuring another storage mount.
7. Static generation and runtime rendering
Static deployment
For a fully static site, generate every required card during the build and publish the resulting files. A static host cannot depend on a live server route in the same way as an SSR deployment.
SSR or Nitro deployment
For runtime generation, the server must execute the module route and store or serve the result. Confirm that your deployment preset supports the selected renderer and that cache storage survives restarts if you need stable performance.
The maintainer source warns when SSR is disabled. Treat that warning as a deployment issue to resolve, not as proof that cards are available at runtime.
8. Security settings for generated URLs
The source documents URL signing and a strict security mode. Strict mode requires an explicit secret, disables inline HTML options, limits query size by default, and restricts runtime images to the origin by default. Use a stable signing secret across rolling or multi-instance deployments, and keep it on the server rather than in client-side code.
Read the security section of the maintainer repository for the exact options in your installed version. Do not copy a secret into public runtime configuration.
9. Use Nuxt Image for assets, not card generation
Nuxt Image is described as an image resizing and transformation tool that can produce responsive sizes and formats such as WebP and AVIF. It can optimize a photo or illustration placed inside your card, but it has a different role from generating a dynamic Open Graph image.
10. Validate the result
- Open the generated image URL directly from an unauthenticated browser or command line.
- Inspect the final page HTML for
og:image,og:title, andog:description. - Share a production URL in the preview debugger for the services that matter to your audience. The Nuxt module listing points to Facebook’s Social Share Debugger and recommends checking behavior across Twitter, Facebook, LinkedIn, and Slack.
- Change the title, regenerate the page, and verify that cache invalidation produces a new image.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
No og:image tag |
SEO metadata is not being emitted or the route is not rendered. | Inspect the final HTML, use an absolute URL, and confirm the page’s SEO setup runs during SSR or prerendering. |
| Image URL returns 404 | The route was not generated, the slug differs, or static output omitted it. | Check the generated route list and deployment output; add the route to prerendering or enable the runtime server. |
| Blank or partially rendered card | Unsupported CSS, missing fonts, or data unavailable at generation time. | Simplify the template, load fonts using the renderer’s supported method, and ensure route data is available before rendering. |
| Old preview persists | Runtime or crawler cache still contains the previous card. | Use a content version in the image URL, respect the configured cache age, and request a fresh preview in the target debugger. |
| Works locally but fails in production | Deployment preset lacks the selected browser/runtime capability, or cache storage is instance-local. | Match renderer to the deployment, configure shared storage where needed, and review SSR-disabled warnings. |
| Signed URL rejected | Secrets differ between instances or strict mode rejects query options. | Set one stable server-side secret and remove inline or oversized query parameters prohibited by strict mode. |
Performance, reliability, and cost notes
- Template rendering is generally easier to cache and scale than launching a browser for every request; choose based on your actual template requirements.
- Generate cards at build time when content changes infrequently. Runtime generation is useful for frequently changing or user-generated content.
- Use deterministic URLs and cache keys so crawlers do not trigger duplicate renders.
- Monitor failed generations, slow data requests, and cache misses. A card that is technically generated but unreachable to crawlers is still a broken preview.
- Keep external API calls out of the card path where possible. If a remote image is required, provide a fallback so one unavailable asset does not invalidate the entire card.
Or skip the browser setup
If you need a screenshot of a Nuxt page or another URL, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status.
See the ScreenshotNeo API documentation for all options, including full-page capture, CSS selectors, dark mode, device presets, retina scale, custom CSS and JavaScript, waits, request blocking, headers, cookies, geolocation, resizing, caching, signed links, asynchronous jobs, webhooks, bulk capture, and PDF output.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/article/my-post -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/article/my-post"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const fs = require('node:fs/promises');
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/article/my-post' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does installing nuxt-og-image automatically add og:image?
No. You still need page metadata that points to the generated image URL, then you must verify the final HTML.
Should every Nuxt site use browser screenshots?
No. A Vue template is usually simpler for repeatable branded cards. Choose browser prerendering when the visual genuinely depends on page layout or browser-only behavior.
Are 1200 × 600 pixels mandatory?
No. That is a documented module default. Treat it as a practical starting point and confirm the dimensions required by your target platforms.
Can Nuxt Image replace nuxt-og-image?
No. Nuxt Image optimizes and transforms image assets; nuxt-og-image generates the social card itself.


