How until was built: live Open Graph images on Cloudflare Workers, in one evening
· Fábio Costa
Countdown pages are a solved problem. What was missing, at least for me, was a countdown link: something you paste into a family WhatsApp group or a team Slack and the preview card itself says "108 days until Christmas 2026". That is the whole product. Type a name and a date, get a link, send it.
What it does
- Creates a countdown page with a live ticker, no account.
- Renders a 1200×630 preview image on request, so the number in the card is right when the link is scraped.
- Gives every countdown a JSON endpoint, an SVG badge and an iframe embed (API).
- Has a studio on every page: start from a vibe, then colours (or take them from a photo of the invitation, in your browser), typefaces, number styles, textures, an emoji, a line of your own. It previews live on the page.
- Optional, €2 once per countdown to save that look, a custom link and no footer; or €4 once for a Pro key that upgrades every countdown you make. Paid via Stripe Checkout.
The stack
One Cloudflare Worker, one D1 database, static assets served by the same Worker, a custom domain attached from wrangler.toml. No framework; the HTML is template strings, the CSS is inlined, the client script is about 60 lines that tick the numbers and fill in the browser's time zone. Everything sits inside the free tier.
The preview image is made with workers-og, which bundles satori (HTML/CSS to SVG) and resvg (SVG to PNG) as WebAssembly. The Worker renders the image per request and caches it for an hour. The image URL carries the current day count as a query string, so when a platform re-scrapes the page it also refetches the image.
Styling without a canvas
The tempting build was a Canva-style editor. It would have been heavy, awkward on a phone, and the preview image has to be re-rendered on the server, which a free-form canvas makes hard. Instead the look is a small JSON object: a preset, three colours, a font pairing, a number style, a texture, a radius, an emoji, a line of text. The server turns it into CSS custom properties on the page and into a satori layout for the image, and the browser applies the same maths live while you fiddle. The photo trick is forty lines: draw the image on a 64-pixel canvas, bucket the colours, pick the dominant one for the background and the most saturated other one for the accent.
Payments without webhooks
The Pro upgrade uses hosted Stripe Checkout. The session is created with the countdown id in its metadata; after Stripe redirects back, the Worker retrieves the session with the secret key and only then applies the upgrade, recording the session id so a reload cannot apply it twice. No webhook secret is needed for that, which mattered because this was built while the account owner was away. A webhook handler can be bolted on later using the same two functions.
Things that went wrong
- satori refuses any element with more than one child unless it has
display: flex, and whitespace between tags counts as a child. The first preview was a 200 response with zero bytes. - "Days until" is a calendar question, not a duration question. 107 days and 22 hours until Christmas is 108 days until Christmas. The server, the client and the image now agree.
- Testing Stripe's return URL needs the real origin; the workers.dev host now derives its own site URL so the test flow can run there.
Honest numbers
Launch day. Countdowns created: a handful, all mine. Paid upgrades: one, in Stripe's test mode with a test card, to prove the flow. Live-mode sales: zero. This paragraph will be updated as real numbers come in.
Who wrote the code
Most of it was written by an AI coding agent (Claude Code) working autonomously from a one-page brief I wrote, including the tests and this post. I am the human who owns the project and the Stripe account, and I am posting it as my own work because it is.