IA & Automation

Generate 50 banners in one go with Node.js - but stop writing your SVG text by hand

A Node.js pipeline produces fifty variations of social visuals in less than a minute. Sharp for compositing and optimization, Satori for text - and everything else you don't want to code by hand.

7 nov 20259 min de lecturePASCAL POTVIN
Écouter l'article

The bottleneck of social visuals

When you're managing the social networks of an active brand, the production of visuals quickly becomes unmanageable. Every publication requires variations for Instagram, X, LinkedIn and the Open Graph image - a minimum of four formats per content. Multiply that by five publications a week, and you end up producing twenty weekly visuals by hand. This pace is unsustainable.

The programmatic approach consists of defining templates as code, injecting dynamic content into them, and automatically generating all the variants. A script that runs in seconds replaces hours of cut-and-paste in Photoshop, and the result is perfectly consistent from one variant to the next. This is the approach I use for my clients: fifty banners from a single template, in less than a minute. But there's one particular place where most tutorials waste your time, and I'll get to that.

Sharp: the composition and optimization engine

Sharp encapsulates libvips, an extremely fast C++ image processing engine - several times faster than pure JavaScript alternatives. It accepts JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG as input and output. Version 0.34, released in Q1 2026, brought things that matter for a production pipeline: first-class AVIF encoding via libavif, native HEIC decoding without a separate license key, a hardened WASM build to reduce cold starts in serverless, and a pool of process workers. In concrete terms, this makes serverless deployment much cleaner than it was a year ago.

The API is a chain of transformations: load a source, resize, compose layers, export.

js import sharp from "sharp";

await sharp("background.jpg") .resize(1200, 630) .composite([ { input: logoBuffer, gravity: "northwest" }, { input: textSvgBuffer, gravity: "center" }, ]) .toFormat("webp", { quality: 80 }) .toFile("output.webp");


Its limitation is that it has no native text rendering. To add text, you have to pass it an SVG overlay, which it then rasterizes - and that's exactly where many get bogged down.

## For text, don't write SVG by hand: Satori

Building a text SVG fragment by hand, calculating positions, line breaks and font adjustment, is tedious and fragile. As soon as the text is dynamic - an article title of variable length - it breaks. The right answer in 2026 is Satori, Vercel's library (version 0.18) that converts HTML and CSS to SVG without a headless browser, supporting flexbox and a large subset of CSS. It's the engine behind `@vercel/og` (version 0.11), which powers thousands of Next.js social maps.

```jsx
// Satori: you're writing JSX/CSS, not SVG <text> by hand
const svg = await satori(
  <div style={{ display: "flex", flexDirection: "column", padding: 64,
                background: "#0b1020", color: "white", width: 1200, height: 630 }}>
    <h1 style={{ fontSize: 64, fontWeight: 700 }}>{title}</h1>
    <p style={{ fontSize: 28, opacity: 0.8 }}>{excerpt}</p>
  </div>,
  { width: 1200, height: 630, fonts: [/* ... */] }
);

My rule became simple: Satori produces the text SVG with a real layout, and Sharp takes care of what it does best - compositing that SVG onto a background, rasterizing, optimizing and exporting. Each in its own place.

Separating template from content

The key to a maintainable system is to never mix template definition and content. I define each template as a configuration object: dimensions, layers, positions, styles, dynamic text zones. The rendering engine reads this config, loads the assets, injects the data and produces the image. Adding a format is done by adding a configuration object, without touching the rendering code. Tip that saved me on multi-format: I position elements in relative percentages rather than absolute pixels, which means I can switch from one ratio to another without recalculating everything.

As for the choice of engine according to need, here's how I decide.

ToolRoleWhen to use it
Sharpcomposition, rasterization, optimizationfoundation of any pipeline
Satori / @vercel/ogHTML/CSS → SVGany dynamic text visual
node-canvas (Cairo)API Canvas 2D servertext rendering without SVG
Puppeteer / Playwrightscreenshot of an HTML pagevery complex compositions, full CSS
Bannerbear, Cloudinary, PlacidSaaS + API RESTteams with no desire to maintain code

A word about Jimp, often cited as a pure JavaScript alternative: it has been rewritten in TypeScript and released in version 1.x. It's still handy for avoiding native dependencies, but its performance remains well below Sharp's - I only use it for specific deployment constraints.

The production pipeline

In production, I integrate generation into a wider workflow. A webhook is triggered when an article is published in the CMS; the Node.js script retrieves the title, extract and front-page image, generates the visuals for all platforms, optimizes them and deposits them in an S3 bucket behind a CDN. All the social network manager has to do is plan the publication, with the visuals already ready.

Weight optimization is critical: I aim for a maximum of 300 Kb per visual for fast loading on mobile. WebP offers an excellent compromise between quality and weight, and with AVIF now first-class in Sharp 0.34, I use it when the target platform supports it. The gain measured on my projects is clear: two to three hours of weekly graphics work replaced by less than a minute of execution. But the real benefit isn't time - it's consistency. Each visual respects exactly the same margins, fonts and composition rules, without the variability inherent in repeated manual work. And by entrusting the text to Satori rather than to cobbled-together SVG <text>, the system no longer breaks whenever a title exceeds the expected length.

§ COMMENTAIRES

Laisser un commentaire