Project notes
A closer look at the design decisions, technical choices, and problems this project was built to solve.
Open Graph generation without surrendering the design
I built @santi020k/og to separate two concerns that are often bundled together: the mechanics of generating social images and the visual decisions that make those images belong to a product.
The package handles paths, output formats, caching, concurrency, cleanup, and verification. The consuming project supplies its content, assets, fonts, card composition, and renderer. That boundary makes the pipeline reusable without turning every brand into a preset.
Goals
- Keep brand ownership inside each project instead of prescribing a template or content model.
- Make committed social images verifiable with deterministic fingerprints and a read-only CI command.
- Regenerate only what changed across collections that may contain hundreds of cards.
- Stay framework-agnostic so the same pipeline works with Astro, Next.js, Node.js, a CMS, or a static array.
What I built
- A typed configuration API and CLI with
init,generate,check, and force-generation workflows. - Sharp and Satori entry points for SVG-based designs, HTML-like templates, font loading, and raster encoding.
- Five output families selected by extension: SVG, PNG, WebP, JPEG, and AVIF.
- Content-aware caching that fingerprints card data, dimensions, output names, configuration, templates, fonts, logos, and declared source files.
- Bounded worker-thread generation for large collections without letting native image work overwhelm CI memory.
- Tracked cleanup that removes only obsolete outputs recorded by the previous manifest instead of scanning and deleting arbitrary files.
Technical highlights
- Renderer contract: projects can return SVG, use the bundled Sharp or Satori adapters, or provide a completely custom renderer.
- Safe paths: output files and the cache manifest are constrained to the project root to prevent traversal.
- Deterministic CI:
santi-og checkreports missing or stale outputs without mutating the repository. - Portable inputs: card data can come from content collections, Markdown, CMS responses, framework loaders, or any serializable source.
- Parallel builds: worker renderer modules support structured-cloneable data and automatic or fixed concurrency.
Results
- One generation workflow across different stacks without adding a framework runtime.
- Faster repeat builds because unchanged cards keep their existing output.
- Safer repository maintenance through manifest-scoped cleanup and explicit path validation.
- Project-owned visuals that remain free to evolve independently from the generator.
Why it matters
Social images are small artifacts backed by a surprisingly repetitive build system. The valuable shared abstraction is not a universal card design. It is a dependable way to discover inputs, detect changes, render in parallel, encode files, clean obsolete artifacts, and prove that committed output is current.
@santi020k/og owns that machinery and stops at the boundary where a project’s identity begins.


