Heading text on the image, rendered at 76px. Trimmed, and truncated to 100 characters with a trailing ellipsis.
OG Images
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
/og?title=Hello&breadcrumb=DemoDrop 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.
/og?title=Authentication&breadcrumb=Getting%20Started&description=Set%20up%20JWT%20sessions&theme=darkEvery 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.
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.
{ "error": "og-render-failed" }Options
titlequeryDefault: OwnDocsbreadcrumbqueryBreadcrumb text above the title. Trimmed and truncated to 80 characters; the block is omitted when empty.
descriptionquerySubtitle below the title. Trimmed and truncated to 200 characters; the block is omitted when empty.
themequeryDefault: lightlight or dark. Any other value falls back to light.
THEME_VARIANTenvDefault: violetServer 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_URLenvAbsolute host used to build the image URL in page metadata. Falls back to
VERCEL_PROJECT_PRODUCTION_URL, then https://localhost:3000.
widthnumberDefault: 1200Fixed output width in pixels.
heightnumberDefault: 630Fixed output height in pixels, giving the 1.91:1 ratio social platforms crop to.