Skip to main content

OG Images

4 min readStableIntermediate

Every page exposes a 1200×630 PNG generated on demand by the /og route handler using next/og. Page metadata sets the image as the OpenGraph and Twitter card image. Nothing is checked into the repo and no third-party service is called.

Quick Start

Hit the route with a title and you get a PNG back.

/og?title=Hello&breadcrumb=Demo

Plain Text
Plain Text
/og?title=Hello&breadcrumb=Demo

Drop the title and the image still renders — title falls back to OwnDocs, so a bare /og is a valid site-wide card.

Adding Options

Combine the title, breadcrumb, description, and theme parameters. Values are URL-encoded, so spaces become %20.

Plain Text
Plain Text
/og?title=Authentication&breadcrumb=Getting%20Started&description=Set%20up%20JWT%20sessions&theme=dark

Every value is trimmed before it renders, and a parameter that trims down to an empty string is treated as absent: title returns to OwnDocs, while breadcrumb and description drop their blocks from the layout entirely. theme accepts light and dark and nothing else — a misspelled or missing value silently renders light rather than erroring.

Five things get drawn, top to bottom: a rounded accent square holding the letter C beside the OwnDocs wordmark, the breadcrumb line, the title at 76px, the description at 28px, then a footer rule with Documentation on the left and owndocs in the accent color on the right.

Advanced

You rarely call /og by hand. generateMetadata in the catch-all route builds the URL for every page from the page title, its breadcrumb label, and its description, then hands it to both the OpenGraph and Twitter card. It never sets theme, so page images render in the light palette.

app/[[...slug]]/page.tsx
TypeScript
const ogParams = new URLSearchParams({ title: plainTitle })
if (breadcrumb) ogParams.set('breadcrumb', breadcrumb)
if (description) ogParams.set('description', description)
const ogImageUrl = `${siteUrl}/og?${ogParams.toString()}`

The values it passes are the resolved ones: title is the frontmatter title, falling back to the first # heading and then to OwnDocs; description is the frontmatter description, falling back to a 160-character extract of the body; and breadcrumb is the route's segments run through the same acronym-aware formatter the visible breadcrumbs use, joined with /. The absolute host comes from NEXT_PUBLIC_SITE_URL, then VERCEL_PROJECT_PRODUCTION_URL, then https://localhost:3000. The same URL is registered twice — once under openGraph.images with width, height, and the plain title as alt, and once under twitter.images with a summary_large_image card.

Colors come from the palette named by THEME_VARIANT, so the image matches the site: background, text, and border read from the variant's light or dark token set, and the brand square and footer accent use --color-accent.

Truncation cuts one character short and appends an ellipsis, so a 140-character title ships as 99 characters plus . The limits are 100 for the title, 80 for the breadcrumb, and 200 for the description.

The handler runs on the Node.js runtime and answers GET only. If next/og throws while rendering, the route returns a 500 with a JSON body of {"error":"og-render-failed"} and cache-control: no-store, so a bad frame never gets cached.

Render failure response
JSON
{ "error": "og-render-failed" }

Options

titlequeryDefault: OwnDocs

Heading text on the image, rendered at 76px. Trimmed, and truncated to 100 characters with a trailing ellipsis.

breadcrumbquery

Breadcrumb text above the title. Trimmed and truncated to 80 characters; the block is omitted when empty.

descriptionquery

Subtitle below the title. Trimmed and truncated to 200 characters; the block is omitted when empty.

themequeryDefault: light

light or dark. Any other value falls back to light.

THEME_VARIANTenvDefault: violet

Server env var naming the palette. Supplies the background, text, tertiary text, accent, and border colors used in the image. An unknown name falls back to violet.

NEXT_PUBLIC_SITE_URLenv

Absolute host used to build the image URL in page metadata. Falls back to VERCEL_PROJECT_PRODUCTION_URL, then https://localhost:3000.

widthnumberDefault: 1200

Fixed output width in pixels.

heightnumberDefault: 630

Fixed output height in pixels, giving the 1.91:1 ratio social platforms crop to.

Was this page helpful?