Examples
Open Graph image
1200×630 social-share images at single-digit-millisecond render time.

OG images are Sone's natural domain — fixed-size, programmatic, generated per request. The example below is a realistic blog-post card: brand mark and category eyebrow up top, title with one inline accent span, author block and read-time at the bottom.
import { Column, Photo, Row, Span, sone, Text } from "sone";
const ACCENT = "#0DF";
const FG = "#0a0a0a";
const FG_MUTED = "#525252";
type OgProps = {
brand: { name: string; logo: string };
category: string;
title: string;
highlight: string;
excerpt: string;
author: { name: string; role: string; avatar: string };
readTime: number;
};
function OgImage({ brand, category, title, highlight, excerpt, author, readTime }: OgProps) {
// Split the title at `highlight` so the punch line gets the accent color.
const [before, after] = title.split(highlight);
return Column(
// Header — brand mark + category eyebrow
Row(
Row(
Photo(brand.logo).width(32).height(32).rounded(6),
Text(brand.name).size(16).weight("bold").color(FG).letterSpacing(0.2),
)
.gap(10)
.alignItems("center"),
Text(category).size(12).weight("bold").color(FG).letterSpacing(1.6),
)
.justifyContent("space-between")
.alignItems("center"),
// Title + excerpt
Column(
Text(before, Span(highlight).color(ACCENT).weight("bold"), after)
.size(64)
.weight("bold")
.lineHeight(1.1)
.color(FG)
.maxWidth(1040),
Text(excerpt)
.size(20)
.lineHeight(1.4)
.color(FG_MUTED)
.maxWidth(900),
).gap(18),
// Footer — author block + read time
Row(
Row(
Photo(author.avatar).width(40).height(40).rounded(20),
Column(
Text(author.name).size(15).weight("bold").color(FG),
Text(author.role).size(13).color(FG_MUTED),
).gap(2),
)
.gap(12)
.alignItems("center"),
Text(`${readTime} MIN READ`).size(12).weight("bold").color(FG_MUTED).letterSpacing(1.6),
)
.justifyContent("space-between")
.alignItems("center"),
)
.width(1200)
.height(630)
.padding(64)
.bg("white")
.justifyContent("space-between");
}
const buffer = await sone(
OgImage({
brand: { name: "sone.dev", logo: "./logo.png" },
category: "ENGINEERING",
title: "How we cut PDF render time from 2 seconds to 30 milliseconds",
highlight: "30 milliseconds",
excerpt: "An honest look at the architectural changes that made our document pipeline 60× faster.",
author: { name: "Alex Chen", role: "Engineering Lead", avatar: "./alex.jpg" },
readTime: 8,
}),
).jpg(0.92);Notes
- Inline
Spanin the title is how Sone does selective emphasis. One bold-and-colored phrase reads as the headline punch without needing a separate element above or below. .justifyContent("space-between")on the outer column spaces the three rows evenly across the 630 px canvas — the title block grows to fill whatever's left..jpg(0.92)is the right output for OG. Most social platforms re-encode the image anyway; quality 0.92 keeps file size well under 200 KB and is visually indistinguishable from quality 1.- Caching — for high-traffic blogs, key the rendered buffer by
(title, highlight, author, readTime). Identical inputs always produce identical bytes.
In a request handler
// Hono / Express / Next.js — render fresh per request
app.get("/og", async (req, res) => {
const buffer = await sone(
OgImage({
brand: { name: "sone.dev", logo: "./public/logo.png" },
category: req.query.category as string,
title: req.query.title as string,
highlight: (req.query.highlight as string) ?? "",
excerpt: req.query.excerpt as string,
author: {
name: req.query.author as string,
role: req.query.role as string,
avatar: `./public/avatars/${req.query.authorId}.jpg`,
},
readTime: Number(req.query.readTime),
}),
).jpg(0.92);
res.setHeader("Content-Type", "image/jpeg");
res.setHeader("Cache-Control", "public, max-age=3600, s-maxage=86400");
res.end(buffer);
});Single-digit-millisecond render means you can afford to render on every request — no need for build-time pre-rendering or background workers.