Embedding OG Images
The generated URL for an OG image depends on the options passed to defineOgImage(), including the component hash and any props. These URLs change when you edit a template, so hardcoding them in a blog index or listing page breaks on every deploy.
The /_og/r/ endpoint solves this. Point it at any page path and it fetches the page, reads the og:image meta tag, and redirects (302) to the current image URL.
<template>
<article v-for="post in posts" :key="post.path">
<NuxtLink :to="post.path">
<img :src="`/_og/r${post.path}`" :alt="post.title">
<h3>{{ post.title }}</h3>
</NuxtLink>
</article>
</template>Browsers follow the 302 transparently, so the image renders exactly as it would on the original page.
Using The Current Page's URL
If you're showing the OG image on the same page that declares it, skip the resolver entirely. defineOgImage() returns the generated URLs:
<script lang="ts" setup>
const [ogImageUrl] = defineOgImage('NuxtSeo.takumi', { title: 'Hello' })
</script>
<template>
<img :src="ogImageUrl" alt="Preview">
</template>The return type is string[] with one URL per variant. See defineOgImage() for details.
Selecting twitter:image
By default the resolver follows og:image. Pass _og_key=twitter to follow twitter:image instead:
<img src="/_og/r/blog/hello-world?_og_key=twitter">Dynamic Pages
Query parameters on the resolver URL (other than _og_*) are forwarded to the target page fetch, so dynamic routes render against the right props:
<img src="/_og/r/products?id=42">This fetches /products?id=42, then redirects to whatever og:image that page produced.
Image Extension Suffix
Some tools refuse to treat a URL as an image unless it ends in an image extension. The resolver strips a trailing .png, .jpg, .jpeg, .webp, or .svg before resolving:
<img src="/_og/r/blog/hello-world.png">The extension is cosmetic. The redirect target is whatever the page declares.
Caching
The resolver emits Cache-Control: public, max-age=<ttl>, s-maxage=<ttl>, immutable using your module's cacheMaxAgeSeconds, so CDNs cache the redirect without revalidation for the configured TTL.
To override the default, set your own headers via route rules:
export default defineNuxtConfig({
routeRules: {
'/_og/r/**': { headers: { 'cache-control': 'public, max-age=3600' } }
}
})Security
The resolver respects the same settings as the image handlers. See the security guide for configuration.
| Protection | Behavior |
|---|---|
security.restrictRuntimeImagesToOrigin | Blocks requests from unknown hosts |
security.maxQueryParamSize | Caps the forwarded query string |
| Internal route guard | Rejects paths starting with /_ |
| Scheme and protocol-relative guard | Rejects //evil.com and http:// paths |
| Path length cap | Rejects paths over 2048 characters |
The redirect target is whatever the page's own og:image declares. The resolver does not validate the destination, so its trust boundary is the set of pages on your site that call defineOgImage() or set og:image via useSeoMeta().