ELEVENTY · BUILD-TIME OG IMAGES

Let every 11ty page leave the build with its own social card.

Render images during the Eleventy build, save them beside the generated site, and publish ordinary static image URLs. The API key exists only in CI; social crawlers never see it and your production site needs no server function.

Create a small build script

Node 20 includes fetch, so the integration needs no HTTP dependency. This function sends the API key in a header, writes the returned PNG to the Eleventy output directory, and throws a readable error if the API rejects the request.

import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; export async function renderOg(page) { const query = new URLSearchParams({ title: page.data.title, author: page.data.author || "Your team", site: "example.com", }); const response = await fetch(`https://ogshot.dev/v1/template/article?${query}`, { headers: { "x-api-key": process.env.OGSHOT_API_KEY }, }); if (!response.ok) throw new Error(`ogshot failed: ${response.status} ${await response.text()}`); const output = `_site/og/${page.fileSlug}.png`; await mkdir(dirname(output), { recursive: true }); await writeFile(output, Buffer.from(await response.arrayBuffer())); return `/og/${page.fileSlug}.png`; }

Collect the pages that deserve cards

Run the renderer from an Eleventy event after the content graph is available, or from a prebuild script that reads the same data files as the site. Exclude pagination helpers, tag archives, and utility pages unless they are meant to be shared. A deterministic filename based on fileSlug keeps each page URL stable across builds.

For a large site, keep a small manifest containing the render parameters and their hash. Skip the API call when the hash and output file already exist. ogshot also caches identical requests and does not count cache hits against the quota, but avoiding the network call makes local builds faster.

Store the generated path in page data or derive it from the same slug in the base layout. Use an absolute URL for og:image and include width and height so validators can understand the asset without downloading it first.

<meta property="og:title" content="{{ title }}"> <meta property="og:description" content="{{ description }}"> <meta property="og:image" content="https://example.com/og/{{ page.fileSlug }}.png"> <meta property="og:image:width" content="1200"> <meta property="og:image:height" content="630"> <meta name="twitter:card" content="summary_large_image">

Treat the content fields as the cache key

A change to title, author, theme, accent, logo, or template should regenerate the image. A body-copy change usually should not. Hash exactly those inputs so the build pipeline avoids stale cards without rerendering unrelated pages. When a design system changes globally, add a manual design version to every hash.

Never write OGSHOT_API_KEY into an Eleventy data file that becomes public. Keep it in the CI environment and use it only from the Node build process.

Inspect the deployed page

The generated PNG is only half of the integration. Deploy a page and run the public URL through the OG image checker. Confirm that the metadata URL is absolute, returns HTTP 200 with an image content type, is at least 1200×630, and remains legible in each platform crop.

Related guides: Hugo build-time cards, Astro content collections, and Bannerbear alternative comparison.

ELEVENTY · STATIC OUTPUT, DYNAMIC DESIGN

Put the first card into your next build.

One hundred renders are included free each month.