Skip to content

Instantly share code, notes, and snippets.

@cassidoo
Created June 13, 2025 04:00
Show Gist options
  • Save cassidoo/de9529ff2c5129b5e9a30a268c30b9ab to your computer and use it in GitHub Desktop.
Save cassidoo/de9529ff2c5129b5e9a30a268c30b9ab to your computer and use it in GitHub Desktop.
My Open Graph image generation setup
import puppeteer from "puppeteer";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import getPosts from "...";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function generateOGImages() {
const posts = await getPosts();
const templatePath = path.join(__dirname, "../../dist/open-graph/index.html");
const distDir = path.join(__dirname, "../../dist");
console.log("Generating OG images...");
console.log(`Found ${posts.length} slugs.`);
const browser = await puppeteer.launch();
// Set the limit when testing
// let limit = 30;
for (const post of posts) {
// if (limit-- <= 0) {
// console.log("Limit reached, stopping generation.");
// break;
// }
const page = await browser.newPage();
const url = `file://${templatePath}?title=${encodeURIComponent(post.title)}`;
await page.goto(url);
await page.setViewport({ width: 1200, height: 630 });
const outputDir = path.join(distDir, "og-image");
await fs.mkdir(outputDir, { recursive: true });
await page.screenshot({
path: path.join(outputDir, `${post.id}.png`),
type: "png",
});
await page.close();
}
await browser.close();
}
generateOGImages().catch(console.error);
<div class="svg-container">
<img src="../img/blank-card-opt.svg" class="svg-image" />
<div class="svg-text-group">
<div class="svg-overlay-text" id="title"></div>
<div class="svg-overlay-text-sub">a blog post by cassidoo</div>
</div>
</div>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.svg-container {
position: relative;
margin: 0;
padding: 0;
display: inline-block;
}
.svg-image {
display: block;
width: 1200px;
height: 630px;
height: auto;
}
.svg-text-group {
position: absolute;
top: 140px;
left: 60px;
max-width: 960px;
}
.svg-overlay-text {
font-family: "iA Writer Mono", monospace;
font-size: clamp(3em, 3vw, 8em);
text-wrap: balance;
line-height: 1.2;
background: #fff;
padding: 20px 0 40px 20px;
word-break: break-word;
overflow-wrap: break-word;
}
.svg-overlay-text-sub {
font-family: "iA Writer Mono", monospace;
font-size: 3em;
line-height: 1.5;
color: #96979c;
background: #fff;
padding-left: 20px;
margin-top: -5px;
}
</style>
<script>
function decodeHtmlEntities(text) {
const textarea = document.createElement("textarea");
textarea.innerHTML = text;
return textarea.value;
}
const params = new URLSearchParams(window.location.search);
const rawTitle = params.get("title") || "Untitled";
const title = decodeHtmlEntities(rawTitle);
document.getElementById("title").textContent = title;
document.addEventListener("DOMContentLoaded", () => {
const textGroup = document.querySelector(".svg-text-group");
const overlayText = document.querySelector(".svg-overlay-text");
const overlayTextSub = document.querySelector(".svg-overlay-text-sub");
if (overlayText) {
const minSize = 3;
const maxSize = 7.5;
const maxHeight = 400;
let fontSize = maxSize;
overlayText.style.fontSize = `${fontSize}em`;
// Binary search for optimal font size
let low = minSize;
let high = maxSize;
while (high - low > 0.1) {
fontSize = (low + high) / 2;
overlayText.style.fontSize = `${fontSize}em`;
overlayText.offsetHeight;
if (overlayText.scrollHeight > maxHeight) {
high = fontSize;
} else {
low = fontSize;
}
}
overlayText.style.fontSize = `${low}em`;
// Calculate lines after final sizing
const lineHeight = parseFloat(getComputedStyle(overlayText).lineHeight);
const lines = Math.ceil(overlayText.offsetHeight / lineHeight);
if (lines >= 3) {
textGroup.style.top = "60px";
}
if (lines <= 2) {
overlayTextSub.style.paddingBottom = "50px";
}
// Adjust height to be multiple of 100px
const computedHeight = textGroup.offsetHeight;
const roundedHeight = Math.ceil(computedHeight / 100) * 100;
textGroup.style.height = `${roundedHeight}px`;
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment