Next.js
Generate Next.js OG Images with VectorFast Render
Add on-brand Open Graph images to a Next.js App Router site with VectorFast Render, using generateMetadata or opengraph-image, signed URLs or the REST API, and a fair comparison with next/og.
Published 2026-10-30 · Markdown version
VectorFast Render is in early access. Billing is not connected yet, and API keys are issued to waitlist members as access opens. Join the waitlist
Short answer: in a Next.js App Router page, return a VectorFast Render image URL from generateMetadata as openGraph.images. Get the URL either by calling POST /v1/renders on the server (works in early access) or by signing a /i/blog-post.png URL on the server (once per-key signing secrets are issued). Either way the image is rendered once and then served from cache.
This guide uses the blog-post template (1200×630). Any template works the same way. It was written against the Next.js docs for generateMetadata and opengraph-image (Next.js 16, pages last updated 2026-08-19 and 2026-07-09, read on 2026-10-30).
Before you start
- A VectorFast Render API key in
VECTORFAST_API_KEY(see the quickstart). It is a server secret: read it only in Server Components, route handlers, andgenerateMetadata, never in a'use client'file. - Optional: a brand kit id in
VECTORFAST_BRAND_KIT. Without it, renders use your default brand kit.
Option 1: call the REST API in generateMetadata
Create a small server-only helper:
import 'server-only' // npm install server-only: fails the build if a Client Component imports this file
interface OgCard {
title: string
excerpt?: string
category?: string
author?: string
}
export async function ogImageUrl(card: OgCard): Promise<string | null> {
const response = await fetch('https://vectorfast.ai/v1/renders', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VECTORFAST_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'blog-post@1',
width: 1200,
height: 630,
brand_kit: process.env.VECTORFAST_BRAND_KIT || undefined,
params: card,
}),
// The same params always return the same URL, so the response can be cached.
cache: 'force-cache',
})
if (!response.ok)
return null
const render = await response.json() as { url: string }
return render.url
}Use it in the page:
import type { Metadata } from 'next'
import { getPost } from '@/lib/posts'
import { ogImageUrl } from '@/lib/og-image'
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
const image = await ogImageUrl({
title: post.title,
excerpt: post.summary,
category: post.category,
author: post.author,
})
return {
title: post.title,
openGraph: {
images: image ? [{ url: image, width: 1200, height: 630, alt: post.title }] : [],
},
}
}What happens:
- The first time a post's metadata is generated, VectorFast renders the card and returns a signed
url. - The returned URL is immutable: crawlers and chat apps that fetch it get a one-year
Cache-Control: immutableresponse. - The same title, excerpt, and brand kit version always produce the same cache key, so regenerating the page (a rebuild, a revalidation) returns
cached: trueand does not render again. Once billing is connected, those cache hits cost nothing. - If the API is unreachable, the helper returns
nulland the page still renders without an image; handle that however your site prefers, for example with a static fallback image.
Text longer than a parameter's limit is rejected (title is at most 110 characters for blog-post), so trim long titles before sending them.
Option 2: sign the image URL on your server
A signed URL is computed locally with an HMAC, so it costs no network request at build or render time. The image is rendered the first time someone fetches the URL.
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.
Copy signRenderUrl from Signed URLs into lib/sign-render-url.ts, then:
import type { Metadata } from 'next'
import { getPost } from '@/lib/posts'
import { signRenderUrl } from '@/lib/sign-render-url'
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
const url = signRenderUrl({
template: 'blog-post',
width: 1200,
height: 630,
brand: process.env.VECTORFAST_BRAND_KIT_VERSION, // for example "bk_…@3"
params: { title: post.title, excerpt: post.summary, category: post.category },
})
return {
title: post.title,
openGraph: { images: [{ url, width: 1200, height: 630, alt: post.title }] },
}
}Signed URLs for an account brand kit must pin a version (bk_…@3), so a stored URL never changes pixels. Bump the version in your environment when you change the kit.
Option 3: keep an opengraph-image route
If your site already uses the opengraph-image.tsx file convention, you can keep the route and have it return the VectorFast image. The default export must return a Response:
import { getPost } from '@/lib/posts'
import { ogImageUrl } from '@/lib/og-image'
export const alt = 'Blog post'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
const url = await ogImageUrl({ title: post.title, excerpt: post.summary })
if (!url)
return new Response('Image unavailable', { status: 502 })
const image = await fetch(url)
return new Response(image.body, { headers: { 'Content-Type': 'image/png' } })
}Next.js caches generated opengraph-image routes by default unless they use request-time APIs, so this runs once per post per build or revalidation. The trade-off: your deployment now proxies the image bytes. Options 1 and 2 point crawlers straight at the VectorFast URL instead.
VectorFast Render or next/og?
Next.js ships ImageResponse from next/og (the @vercel/og library), which renders JSX to PNG inside your own functions. It is free, open source, and a good fit when one Next.js app needs OG images and you want to own the layout code.
VectorFast Render fits better when:
- the same on-brand cards are needed outside this one app (other sites, CI, an AI agent);
- you want one versioned brand kit, including a logo traced from a PNG, instead of fonts and colors copied into each project;
- you want renders cached and deduplicated across every caller.
next/og has limits of its own: only flexbox and a subset of CSS, ttf/otf/woff fonts that you load yourself, and a 500 KB bundle limit for the code, fonts, and images in the function (Vercel docs, read 2026-10-30). See the full VectorFast Render vs Vercel OG comparison.
FAQ
Will social apps show the image immediately? The first fetch of a new URL renders the image (tens of milliseconds in local tests, plus font loading; edge timings are not published yet); later fetches are cache hits.
Can I use a 1080×1080 image for LinkedIn or Instagram? Yes. Pick a template that supports that size, such as launch-post or social-square, and pass width and height.
What if a title has Chinese or Japanese characters? Templates load a CJK font subset automatically when the text needs it.