TL;DR: Stop manually designing cover images or relying on generic placeholders for your Open Graph (OG) previews. In this guide, we will programmatically build a pipeline using Vercel’s Satori and Sharp to automatically generate branded, dynamic social cards directly at build time in Astro (or any Node.js environment).
What is Satori and Why Automate Social Images?
An Open Graph (OG) image is the visual preview that displays when you share a link on social media platforms and communication apps like WhatsApp, LinkedIn, Twitter (X), or Slack. Whenever someone drops your link into a chat or feed, these preview cards are the very first thing their audience sees.

Satori is a library created by Vercel that converts HTML and CSS (structured via JSX) into an SVG. Sharp is a high-performance Node.js image processing library that takes Satori’s SVG and renders it into a web-optimized JPEG or PNG. Combining them allows you to automate high-quality social previews without manual design effort or costly SaaS APIs.
Why “Good Enough” Social Previews are Hurting Your CTR
For the longest time, many of us followed the easiest route: using the blog post’s generic hero image as the social preview. While simple, this approach fails to communicate the value of the post.
When someone scrolls past your link on Twitter or LinkedIn, or opens a shared link on WhatsApp, a beautiful but context-free stock photo doesn’t grab attention. You are relying on tiny metadata text underneath to explain what your article is about. By automating social cards, your previews dynamically incorporate the title, description, and branding, dramatically boosting click-through rates (CTR) and maintaining a consistent visual identity across the web.
The Satori + Sharp Architecture
The pipeline consists of three clear phases:
- Asset Loading: Satori requires raw font files (such as
.ttf) loaded into buffers to render typography correctly. - Layout & SVGs (Satori): You define the visual structure of your card using a simplified subset of CSS flexbox in a React-like JSX structure.
- Rasterization (Sharp): The raw SVG code is passed into Sharp to convert it into a light, progressive JPEG or PNG image ready for social sharing.
Step-by-Step Implementation
Let’s build this pipeline. While this guide shows a setup optimized for an Astro dynamic endpoint, you can adapt it to any Node.js, Next.js, or Express server.
1. Load Your Typography Files
Because Satori does not have access to system fonts or Google Fonts APIs at runtime, you must provide your font files as a raw buffer.
import fs from 'fs';
import path from 'path';
// Load local font files once into memory
const fontBoldData = fs.readFileSync(path.resolve('src/assets/fonts/Inter-Bold.ttf'));
const fontRegularData = fs.readFileSync(path.resolve('src/assets/fonts/Inter-Regular.ttf'));2. Define the Card Visual Layout with Satori
Satori parses a nested object representing JSX nodes and renders them into an SVG string. Satori only supports standard flexbox properties. Here is how we construct a clean, modern dark-themed card template:
import satori from 'satori';
const svg = await satori(
{
type: 'div',
props: {
style: {
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#0a0a0a',
padding: '60px 80px',
fontFamily: 'Inter',
justifyContent: 'space-between',
},
children: [
{
type: 'div',
props: {
style: {
display: 'flex',
flexDirection: 'column',
},
children: [
{
type: 'div',
props: {
style: {
fontSize: '64px',
color: '#ffffff',
fontWeight: 'bold',
lineHeight: '1.2',
},
children: title,
},
},
{
type: 'div',
props: {
style: {
fontSize: '28px',
color: '#a3a3a3',
marginTop: '24px',
lineHeight: '1.4',
},
children: description,
},
},
],
},
},
{
type: 'div',
props: {
style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
borderTop: '2px solid #262626',
paddingTop: '32px',
},
children: [
{
type: 'span',
props: {
style: { fontSize: '24px', color: '#3b82f6', fontWeight: 'bold' },
children: "nkaushik.in",
},
},
],
},
},
],
},
},
{
width: 1200,
height: 630,
fonts: [
{ name: 'Inter', data: fontRegularData, weight: 400, style: 'normal' },
{ name: 'Inter', data: fontBoldData, weight: 700, style: 'normal' },
],
}
);3. Convert SVG to High-Quality JPEGs with Sharp
Since social networks do not natively support SVG files for Open Graph tags, we use Sharp to convert the SVG output buffer into a highly compressed JPEG.
import sharp from 'sharp';
const jpegBuffer = await sharp(Buffer.from(svg))
.jpeg({ quality: 85, progressive: true })
.toBuffer();
// Return the direct binary response with correct MIME types
return new Response(jpegBuffer, {
headers: {
'Content-Type': 'image/jpeg',
'Cache-Control': 'public, max-age=31536000, immutable',
},
});Frequently Asked Questions
What is Vercel Satori?
Satori is a library developed by Vercel that converts HTML and CSS code (passed as React-like JSX element structures) into clean SVGs. It features a custom, lightweight CSS layout engine that implements a subset of Flexbox.
Why do I need Sharp alongside Satori?
Satori only outputs vector SVGs. Social media crawlers (like X, LinkedIn, and Facebook) require raster images (such as JPEGs or PNGs) in their <meta property="og:image"> tags. Sharp is an ultra-fast, native C++ wrapper for Node.js that converts Satori’s vector SVGs to high-performance raster images in milliseconds.
Does Satori support Tailwind CSS?
Yes, Satori has built-in support for Tailwind utility classes using the tailwind property in the options argument, making it simple to style elements using classes instead of inline style objects.
The Benefits of Automated Images
Implementing automated OG generation delivers immediate improvements to your workflow:
- Zero Friction: Write your content in markdown or your CMS, and let your site code handle visual assets.
- Perfect Visuals: Images look identical across all screens, utilizing your exact brand fonts and hex codes.
- Faster Page Builds: When caching is added, dynamic builders generate these once, ensuring minimal compilation overhead.


