Four ways to make
a share image.
the short answer
A share image is either drawn in the visitor's browser or drawn on a server, and that fork decides everything else. A custom element for share images belongs to the first branch: the tag renders the card in the page, and the same tag exports it as a PNG when someone clicks. The second branch — satori, or @vercel/og wrapping it — draws the image without a browser, which is the only way an Open Graph card can work, because the crawler that fetches it will never run your JavaScript.
So the question is not which library is better. It is whether the person the image is for is present. A user staring at their listening history and wanting a PNG of it is present, and the browser already has the receipt laid out. A social crawler asking for a preview card is not present, and there is no layout to reuse.
the size difference, and why
The client-side packages are two orders of magnitude smaller than the server ones — 186 KB and 315 KB unpacked against 5.43 MB and 6.89 MB. That gap is structural rather than a matter of care. Rendering in a browser means borrowing the layout engine that is already in the room, which is what the <foreignObject> technique actually does. Rendering on a server means there is no layout engine to borrow and one has to be shipped.
route · version, deps, licence, size, published · when it wins
a custom element | one script tag · 0 deps · MIT | The element renders the thing AND exports it. Right when the user is looking at the artefact and wants a PNG of it on click. Tearline is 17,991 bytes served, HTTP 200 on 13 Aug 2026. |
modern-screenshot | 4.7.0 · 0 deps · MIT · 186 KB · 16 Apr 2026 | A rasteriser you point at any node you already have on screen. Right when the share image is an existing part of the page rather than a purpose-built card. |
html-to-image | 1.11.13 · 0 deps · MIT · 315 KB · 14 Feb 2025 | Same job, longer-standing. Also zero declared runtime dependencies. Both of these leave the markup, the styling and the click handler to you. |
satori | 0.29.0 · 11 deps · MPL-2.0 · 5.43 MB · 23 Jul 2026 | Renders the image on a server, so no browser is involved and no user has to be present. Right for an Open Graph card, which a crawler has to fetch without running your app. |
@vercel/og | 1.0.1 · 2 deps · MPL-2.0 · 6.89 MB · 8 Aug 2026 | The same server route packaged for a framework route handler. Its two declared runtime dependencies are satori and @resvg/resvg-wasm, so the licence is MPL-2.0 here too, not MIT. |
Registry facts, not a review: read from registry.npmjs.org on 9 August 2026. @vercel/og left 0.x four days after this page first ran the table — 1.0.1 published 8 August 2026, two runtime dependencies still, and marginally smaller unpacked at 6.89 MB. One licence detail worth catching before it reaches a legal review — the two client-side rasterisers are MIT, and both server-side packages are MPL-2.0. @vercel/og declares exactly two runtime dependencies, satori and @resvg/resvg-wasm, which is why the licence carries through. Nothing here is a claim about how any of these four work internally: their metadata was fetched, their source was not.