svelte-build-og
v0.3.0
Published
Generate Open Graph images from SvelteKit routes during development and build.
Downloads
25
Readme
svelte-build-og
Generate Open Graph images from ordinary SvelteKit routes. You build the card with your normal components, CSS, fonts, and design tokens; the plugin captures selected routes in Chromium and emits the resulting images with the rest of the application.
Install
pnpm add --save-dev svelte-build-ogChromium is downloaded automatically on the first capture on machines with a writable browser cache. Read-only and serverless environments need an explicit browser provider.
Configure
Add named inputs in vite.config.ts. A static input is just a route. A dynamic input uses SvelteKit's full-segment [param], [[param]], or [...param] syntax, including matchers, and an entries function to select every image that should exist after the build. Embedded forms such as post-[slug] are not supported.
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { og } from "svelte-build-og/vite";
export default defineConfig({
plugins: [
sveltekit(),
og({
input: {
home: "/og/home",
docs: {
link: "/og/docs/[category]/[slug]",
entries: () => [
{ category: "ser", slug: "intro" },
{ category: "ser", slug: "installation" },
],
},
post: {
link: "/og/blog/[year]/[...slug]",
entries: async () => [{ year: "2026", slug: "releases/svelte-build-og" }],
},
},
format: { file: "png", opts: { compressionLevel: 9 } },
size: { x: 1200, y: 630 },
}),
],
});The default capture concurrency is one. Raise it only when the target environment has enough memory for multiple pages in the shared browser context:
og({
capture: { concurrency: 2 },
input: { home: "/og/home" },
});For Vercel and other serverless environments, install @sparticuz/chromium and use the official adapter instead of relying on Playwright's installer:
import chromium from "@sparticuz/chromium";
import { og, serverless_chromium } from "svelte-build-og/vite";
og({
browser: serverless_chromium(chromium),
input: { home: "/og/home" },
});entries can fill any number of dynamic route layers, including rest parameters such as [...slug]. Each returned object creates one output image at the resolved route plus the selected extension.
The format is a discriminated object, so opts is typed as the corresponding Sharp PNG, WebP, or AVIF options:
format: { file: "webp", opts: { quality: 84, effort: 5 } }Chromium always captures a PNG first. Sharp only performs the configured output encoding afterward.
Resolve an image
Import resolved from the package root. The plugin generates types from the named inputs, so each name accepts only its own route parameters.
<script lang="ts">
import { resolved } from "svelte-build-og";
const image = resolved("docs", { category: "ser", slug: "intro" });
</script>
<img src={image} alt="SER introduction card" />Static inputs do not take a parameters object:
const image = resolved("home");resolved respects SvelteKit's configured base and asset paths. It returns a path because that is what <img> expects; Open Graph metadata normally needs an absolute URL, so resolve it against the current page URL:
<script lang="ts">
import { page } from "$app/state";
import { resolved } from "svelte-build-og";
const image = new URL(
resolved("docs", { category: "ser", slug: "intro" }),
page.url,
).href;
</script>
<svelte:head>
<meta property="og:image" content={image} />
<meta name="twitter:image" content={image} />
</svelte:head>For a prerendered application, set SvelteKit's kit.prerender.origin to the production origin so that absolute metadata URLs are generated correctly.
Generated types
The generated declaration lives at .svelte-kit/svelte-build-og/generated.d.ts. Register it through SvelteKit's generated TypeScript configuration so the declaration remains visible without putting generated files in application source:
import { include_og_types } from "svelte-build-og/vite";
const sveltekit_config = {
kit: {
typescript: { config: include_og_types },
},
};If the application changes SvelteKit's kit.outDir, pass the same directory to sveltekit_out_dir:
og({
input: {
home: "/og/home",
},
sveltekit_out_dir: ".generated/svelte-kit",
});The directory is resolved from the Vite root and should remain excluded from version control alongside the rest of SvelteKit's generated output.
On the first Vite start after upgrading from 0.2, build-og removes its old declaration after the replacement has been written. The former default src/.svelte-build-og/generated.d.ts is detected automatically. If 0.2 used a custom types_file, leave that option in place for the first successful 0.3 start so build-og can remove it, then remove the option and the obsolete ignore rule.
Capture lifecycle
In development, generated image paths are served by Vite and captured lazily. Results remain cached until a watched file is added, changed, or removed. During a production build, the plugin starts an internal local Vite server, warms every capture route serially, and waits for Vite's client and SSR dependency requests to settle. A route that fails during cold preparation is retried once after Vite becomes idle. Pages share one browser context and are reused up to the configured concurrency. Capture waits for document.fonts.ready and two animation frames, takes the viewport-sized PNG with animations disabled, encodes the selected format, and emits each image into Vite's client output.
Only routes returned by entries are emitted. The internal renderer uses Vite's serve command, so capture routes should not branch on $app/environment.building or install behavior that exists only in build-specific Vite hooks. Keep them deterministic and make any remote data they need available in the selected mode.
Development
vp install
vp check
vp run test
vp run test:integration
vp packLicensed under BSD-3-Clause.
