# Generate Astro OG Images with VectorFast Render

> Add on-brand Open Graph images to an Astro site with VectorFast Render, signing image URLs or calling the REST API at build time, with caching notes and server-only secrets.

Canonical URL: https://vectorfast.ai/render/guides/astro-og-image
Markdown URL: https://vectorfast.ai/render/guides/astro-og-image.md
Last modified: 2026-10-30

**Short answer:** in your Astro layout, compute the `og:image` URL in the component frontmatter, which runs at build time (or on the server in SSR). Either call `POST /v1/renders` with your API key or sign a `/i/blog-post.png` URL with your signing secret. Secrets read in frontmatter stay on the server; only the finished URL reaches the HTML.

This guide uses the `blog-post` template (1200×630). Any [template](/render/templates) works the same way.

## Before you start [#before-you-start]

* A VectorFast Render API key (see the [quickstart](/render/docs/quickstart)).
* Declare it as a server secret so it is never bundled for the browser. With `astro:env` (Astro docs, read 2026-10-30, say secret server variables are not included in the final bundle):

```js title="astro.config.mjs"
import { defineConfig, envField } from 'astro/config'

export default defineConfig({
  env: {
    schema: {
      VECTORFAST_API_KEY: envField.string({ context: 'server', access: 'secret' }),
      VECTORFAST_BRAND_KIT: envField.string({ context: 'server', access: 'secret', optional: true }),
    },
  },
})
```

Plain `import.meta.env` works too: variables without the `PUBLIC_` prefix are only available in server code.

## Option 1: call the REST API at build time [#option-1-call-the-rest-api-at-build-time]

```ts title="src/lib/og-image.ts"
import { VECTORFAST_API_KEY, VECTORFAST_BRAND_KIT } from 'astro:env/server'

export async function ogImageUrl(params: { title: string, excerpt?: string, category?: string, author?: string }): Promise<string | null> {
  const response = await fetch('https://vectorfast.ai/v1/renders', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${VECTORFAST_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ template: 'blog-post@1', width: 1200, height: 630, brand_kit: VECTORFAST_BRAND_KIT, params }),
  })
  if (!response.ok)
    return null
  const render = await response.json() as { url: string }
  return render.url
}
```

```astro title="src/layouts/Post.astro"
---
import { ogImageUrl } from '../lib/og-image'

const { title, description, category } = Astro.props
const image = await ogImageUrl({ title, excerpt: description, category })
---
<html lang="en">
  <head>
    <title>{title}</title>
    <meta name="description" content={description} />
    <meta property="og:title" content={title} />
    {image && <meta property="og:image" content={image} />}
    {image && <meta property="og:image:width" content="1200" />}
    {image && <meta property="og:image:height" content="630" />}
    <meta name="twitter:card" content="summary_large_image" />
  </head>
  <body><slot /></body>
</html>
```

In a static build, each page calls the API once per build. Rebuilding with the same content returns the same cache key, so VectorFast answers from cache (`cached: true`) and does not render again; once billing is connected, those hits cost nothing. The URL in your HTML is immutable and safe to cache forever.

During a build with many pages, stay under 60 requests per 60 seconds per key: render pages in batches, or reuse URLs you stored from earlier builds.

## Option 2: sign URLs locally [#option-2-sign-urls-locally]

Signing is a local HMAC, so builds make no network calls and the image is rendered the first time a crawler fetches it.

<Callout title="Not available yet">
  Per-key signing secrets are not issued in early access. Until they are, use option 1; its `url` is the same kind of signed URL.
</Callout>

Copy `signRenderUrl` from [Signed URLs](/render/docs/signed-urls#nodejs) into `src/lib/sign-render-url.ts`, declare `VECTORFAST_SIGNING_SECRET` in the `astro:env` schema the same way as the API key, and use it in the frontmatter:

```astro title="src/layouts/Post.astro"
---
import { VECTORFAST_SIGNING_SECRET } from 'astro:env/server'
import { signRenderUrl } from '../lib/sign-render-url'

const { title, description } = Astro.props
const image = signRenderUrl({
  template: 'blog-post',
  width: 1200,
  height: 630,
  params: { title, excerpt: description },
}, VECTORFAST_SIGNING_SECRET)
---
<meta property="og:image" content={image} />
```

If you pass an account brand kit, pin its version (`brand: 'bk_…@3'`); signed URLs reject unpinned account kits.

## Caching summary [#caching-summary]

| Where                        | What is cached                                                    | For how long                                            |
| ---------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------- |
| VectorFast edge cache and R2 | The rendered image, by cache key                                  | Until the template, params, or brand kit version change |
| Browsers and crawlers        | The image URL response                                            | One year (`immutable`)                                  |
| Your build                   | Nothing; each build asks again, and VectorFast answers from cache | —                                                       |

## Astro's own options [#astros-own-options]

Astro does not include an OG image renderer; community integrations built on Satori render PNGs during the build, which keeps everything in the repository and costs nothing per image. VectorFast Render is the better fit when the same branded cards are needed outside the site, when an AI agent should make them, or when you want a single brand kit (with a logo traced from a PNG) shared across projects.

## FAQ [#faq]

**Does this work with Astro's SSR adapters?** Yes. The frontmatter then runs on each request on your server; the API key or signing secret still stays server-side.

**What if the build cannot reach the API?** The helper returns `null` and the page renders without `og:image`. Add a static fallback image if you prefer.

**Can I preview without spending credits?** `POST /v1/previews` renders a watermarked preview that never spends credits, useful while designing the copy.
