Skip to content

Instantly share code, notes, and snippets.

@davext
Created February 12, 2026 04:49
Show Gist options
  • Select an option

  • Save davext/1cf6e0125273e26cbaeacdc07261643f to your computer and use it in GitHub Desktop.

Select an option

Save davext/1cf6e0125273e26cbaeacdc07261643f to your computer and use it in GitHub Desktop.
Cloudflare Worker Dynamic Open Graph Generator
import { Hono } from "hono";
import { ImageResponse } from "workers-og";
const app = new Hono();
/**
* Brand / product configuration
* Change these values (or wire them to env vars) to re-brand the OG generator.
*/
const BRAND_PREFIX = "Blooio";
const DEFAULT_TITLE = "iMessage API for Developers";
const LOGO_URL = "https://bucket.blooio.com/assets/Logo/Blooio-dark-light-v4.png";
/**
* OG image configuration
*/
const OG_WIDTH = 1200;
const OG_HEIGHT = 630;
/**
* Module-level cache so we don’t fetch + base64-encode the logo on every request.
*/
let cachedLogoDataUrl: string | null = null;
/**
* Helper: adjust font size based on title length
*/
function getFontSize(titleLength: number): string {
if (titleLength > 80) return "48px";
if (titleLength > 60) return "56px";
if (titleLength > 40) return "64px";
return "72px";
}
/**
* Helper: split text into multiple lines when too long (word-wrapping at maxLength).
*/
function formatTitle(text: string, maxLength = 50): string {
if (text.length <= maxLength) return text;
const words = text.split(" ");
const lines: string[] = [];
let currentLine = "";
for (const word of words) {
const next = (currentLine + " " + word).trim();
if (next.length <= maxLength) {
currentLine = next;
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
}
if (currentLine) lines.push(currentLine);
return lines.join("\n");
}
/**
* Convert ArrayBuffer -> base64 in a worker-safe way (no Buffer).
* Chunked to avoid argument size limits.
*/
function arrayBufferToBase64(buf: ArrayBuffer): string {
const bytes = new Uint8Array(buf);
const chunkSize = 0x8000; // 32KB
let binary = "";
for (let i = 0; i < bytes.length; i += chunkSize) {
const chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode(...chunk);
}
return btoa(binary);
}
/**
* Fetch + cache the logo as a data URL (or return null if it fails).
*/
async function getLogoDataUrl(): Promise<string | null> {
if (cachedLogoDataUrl) return cachedLogoDataUrl;
try {
const res = await fetch(LOGO_URL);
if (!res.ok) return null;
const buf = await res.arrayBuffer();
const base64 = arrayBufferToBase64(buf);
cachedLogoDataUrl = `data:image/png;base64,${base64}`;
return cachedLogoDataUrl;
} catch (err) {
console.error("Failed to fetch logo:", err);
return null;
}
}
/**
* Small HTML escaping helpers for /preview endpoint
*/
function escapeHtml(text: string): string {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
function escapeHtmlAttr(text: string): string {
return escapeHtml(text).replace(/"/g, "&quot;");
}
function withBrandPrefix(title: string): string {
// Maintain prior behavior: always prepend "<BRAND_PREFIX> - " to the title
return `${BRAND_PREFIX} - ${title}`;
}
app.get("/", async (c) => {
try {
// Query parameters (support long + short aliases)
const rawTitle =
c.req.query("title") || c.req.query("t") || DEFAULT_TITLE;
const description = c.req.query("description") || c.req.query("d") || "";
const title = withBrandPrefix(rawTitle);
const fontSize = getFontSize(title.length);
const formattedTitle = formatTitle(title);
const formattedDescription = description ? formatTitle(description, 80) : "";
const logoDataUrl = await getLogoDataUrl();
const html = {
type: "div",
props: {
style: {
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
padding: "80px",
background:
"linear-gradient(135deg, #020617 0%, #0c1844 50%, #1e3a8a 100%)",
fontFamily: "Inter, system-ui, -apple-system, sans-serif",
},
children: [
// Logo
logoDataUrl
? {
type: "div",
props: {
style: {
display: "flex",
alignItems: "center",
marginBottom: "60px",
},
children: {
type: "img",
props: {
src: logoDataUrl,
width: 280,
height: 80,
style: {
objectFit: "contain",
filter: "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))",
},
},
},
},
}
: null,
// Title + Description
{
type: "div",
props: {
style: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
flex: 1,
gap: "24px",
},
children: [
{
type: "h1",
props: {
style: {
fontSize,
fontWeight: "bold",
color: "white",
lineHeight: 1.2,
margin: 0,
textShadow: "0 4px 12px rgba(0, 0, 0, 0.2)",
whiteSpace: "pre-wrap",
maxWidth: "1000px",
},
children: formattedTitle,
},
},
description
? {
type: "p",
props: {
style: {
fontSize: "32px",
fontWeight: "normal",
color: "rgba(255, 255, 255, 0.85)",
lineHeight: 1.4,
margin: 0,
textShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
whiteSpace: "pre-wrap",
maxWidth: "1000px",
},
children: formattedDescription,
},
}
: null,
].filter(Boolean),
},
},
].filter(Boolean),
},
};
return new ImageResponse(html, {
width: OG_WIDTH,
height: OG_HEIGHT,
headers: {
"Content-Type": "image/png",
"Cache-Control": "public, max-age=31536000, immutable",
},
});
} catch (error) {
console.error("Error generating OG image:", error);
return c.text("Error generating image", 500);
}
});
// Health check
app.get("/health", (c) => c.json({ status: "ok" }));
// Preview page
app.get("/preview", (c) => {
const title = c.req.query("title") || DEFAULT_TITLE;
const description = c.req.query("description") || "";
const imageUrl = `/?title=${encodeURIComponent(title)}&description=${encodeURIComponent(
description
)}`;
const safeTitleAttr = escapeHtmlAttr(title);
const safeDescText = escapeHtml(description);
return c.html(`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>OG Image Preview</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1200px;
margin: 50px auto;
padding: 20px;
}
img {
width: 100%;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.controls { margin-bottom: 20px; }
input, textarea {
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 10px;
font-family: system-ui, -apple-system, sans-serif;
}
textarea { resize: vertical; min-height: 80px; }
label {
display: block;
font-weight: 600;
margin-bottom: 5px;
color: #333;
}
button {
margin-top: 10px;
padding: 10px 20px;
background: #1e3a8a;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover { background: #1e40af; }
</style>
</head>
<body>
<h1>OG Image Preview</h1>
<div class="controls">
<label for="titleInput">Title</label>
<input
type="text"
id="titleInput"
placeholder="Enter title..."
value="${safeTitleAttr}"
/>
<label for="descriptionInput">Description (optional)</label>
<textarea
id="descriptionInput"
placeholder="Enter description..."
>${safeDescText}</textarea>
<button onclick="updatePreview()">Update Preview</button>
</div>
<img src="${imageUrl}" alt="OG Image Preview" />
<script>
function updatePreview() {
const title = document.getElementById('titleInput').value;
const description = document.getElementById('descriptionInput').value;
window.location.href =
'/preview?title=' + encodeURIComponent(title) +
'&description=' + encodeURIComponent(description);
}
document.getElementById('titleInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') updatePreview();
});
</script>
</body>
</html>`);
});
export default app;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment