Index of every page on this site: llms.txt

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.

pages/blog/index.vue
<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:

nuxt.config.ts
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.

ProtectionBehavior
security.restrictRuntimeImagesToOriginBlocks requests from unknown hosts
security.maxQueryParamSizeCaps the forwarded query string
Internal route guardRejects paths starting with /_
Scheme and protocol-relative guardRejects //evil.com and http:// paths
Path length capRejects 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().

Was this page helpful?