Render the og card via og-svg instead of node-canvas - #9
Open
alcor wants to merge 4 commits into
Open
Conversation
Both previous paths were dead: netlify/functions/og.js used node-canvas, and netlify/edge-functions/og.tsx used Deno og_edge but was switched off by pointing build.edge_functions at a directory that does not exist. netlify/zones-svg.js now builds the card as an SVG — one column per zone with the same per-hour gradient, white text on night columns — and og.js POSTs it to the shared renderer. Sent as a body rather than a query payload because these SVGs would otherwise eat into the 16KB URL cap. Deletes og.tsx and the edge_functions override together: removing the override alone would have let the dead og.tsx claim /og, since its own config declares that path. Drops the canvas dependency. Renderer: https://github.com/arfct/og-svg Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
❌ Deploy Preview for timezone-fyi failed.
|
Two problems found by rendering real zone data rather than stubs.
common.js could not be imported outside the bundler: tzdata's package
main is timezone-data.json, so Node rejected the import with
ERR_IMPORT_ATTRIBUTE_MISSING. Adding `with { type: "json" }` makes the
module — and everything importing it — loadable and testable. The import
exists only to force the bundler to include tzdata, per its comment.
With that fixed, real data showed the card was broken. common.js formats
times as `10:30ᴀᴍ` using Latin small capitals (U+1D00, U+1D0D, U+1D18).
That renders fine in a browser with a web font, but essentially no font
ships the Phonetic Extensions block, so resvg drew "10:30" followed by two
missing-glyph boxes. Small capitals are now folded to ordinary capitals in
the card template only, leaving the HTML page's typography alone.
Adds vitest and 13 tests, exercising the real getZoneInfo rather than
fabricated zone objects. The repo had none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both deployments share one code path. The Netlify functions are already plain Request -> Response handlers with no Netlify-specific APIs, so the Worker delegates to them unchanged; Netlify's behaviour is untouched. Asset precedence matches too — static/ is served first, including / itself. Two things had to be fixed for the bundled runtime: timezonecomplete locates its data with require(tzDataName), a deliberately dynamic require its source comments as avoiding browserify problems. Node resolves it at runtime, so Netlify worked by accident; a bundled runtime with no runtime require cannot, and every zone lookup failed with "Timezonecomplete needs time zone data". The data is now handed over explicitly via TzDatabase.init, the library's documented entry point. /og reaches og-svg through a service binding rather than over HTTP. A Worker fetching another Worker on the same workers.dev zone is refused with Cloudflare error 1042. og.js takes an optional env and uses the binding when present, falling back to fetch on Netlify. Also surfaces the upstream status when a render fails, instead of a bare "Could not render card". Verified against the live Netlify deployment: titles match on every path tried, all assets serve, and /og returns a 1200x630 PNG in ~120ms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The if/else that normalizes a zone name or converts a numeric offset sat
under a braceless `if (z.startsWith("GMT+"))`, so it ran for essentially
nothing — and called .startsWith on the numbers that `overrides` yields,
throwing "z.startsWith is not a function".
The surrounding code makes the intent clear: a bare number is an offset in
hours and becomes minutes, which is what timezonecomplete expects; anything
else is a name to look up. Restored both.
Name lookup now tries the value as-is, uppercased, and title-cased per
path segment, so `utc` and `asia/tokyo` resolve rather than failing with
"non-existing time zone name". Unknown names still surface that error.
Zone tokens are also percent-decoded: an IANA name has to encode its slash
to survive the comma-separated path, and nothing decoded it. Labels are
derived from the decoded token too, so an encoded zone no longer reads
"ASIA%2FTOKYO" — while abbreviations stay "JST" and numeric-offset zones
do not label as a number.
/2pm,utc,jst 0 zones -> UTC 2pm, JST 11pm
/2pm,Asia%2FTokyo error -> TOKYO 2pm
/2pm,america%2Fnew_york error -> NEW YORK 2pm
/10:30am,pst,est unchanged
/2pm,london still an error (bare cities are not IANA zones)
27 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces OG card generation with a call to the shared og-svg renderer.
Both previous paths were dead
netlify/functions/og.jsusednode-canvas, a native binding that cannot run on the current runtime.netlify/edge-functions/og.tsxused Deno'sog_edgeand was switched off by pointingbuild.edge_functionsat./netlify/edge-functions-disabled— a directory that does not exist in this repo.So
/oghas had no working implementation.What replaces them
netlify/zones-svg.jsbuilds the card as an SVG string: one column per zone, filled with the same per-hour gradient fromcolorsthat the canvas version used, zone name above centre and local time below, white text on night columns and black on day.og.jsthen POSTs it to the renderer.Sent as a request body rather than a query payload — these SVGs carry a gradient definition plus two text runs per zone, and would otherwise eat into Cloudflare's 16KB URL cap.
One trap worth flagging
og.tsxand theedge_functionsoverride had to be removed together. Removing the override alone would have let Netlify fall back to the defaultnetlify/edge-functions/directory and pick up the deadog.tsx, whose own config declarespath: "/og"— resurrecting the broken implementation and taking the route from the working one.Verified
Rendered against the live renderer across zone counts and both empty cases:
Zone objects were stubbed to the shape
common.jsproduces (zoneStart.hour(),niceZoneName,startString,night).Known issue, pre-existing and not addressed here
getZoneInfocould not be exercised with plainnode.common.js:2doesimport tzdata from "tzdata", and that package'smainistimezone-data.json, so under Node ≥20 it needs awith { type: "json" }import attribute and otherwise fails withERR_IMPORT_ATTRIBUTE_MISSING.This is unchanged by this PR — it sits in the import chain of the old and new
og.jsidentically. Netlify bundles functions with esbuild, which inlines JSON imports, so the deployed function is likely fine. Worth confirming separately, since it also makes the module impossible to unit-test locally.Removed
netlify/edge-functions/og.tsx, thebuild.edge_functionsoverride, and thecanvasdependency.🤖 Generated with Claude Code