feat(www): add OG image generation for blog posts#1120
Conversation
Generate PNG thumbnails from SVG featured images for Open Graph meta tags. Social media platforms don't support SVG for og:image, so we render PNGs at build time using Resvg WASM. Key features: - Strip CSS animations to show final visible state - Force light mode for consistent social previews - Cascade override injection for reliable text colors - Bounded concurrency (4 parallel) using Effection - TTF fonts committed (Inter, JetBrains Mono) for Resvg compatibility The PNGs are generated during CI before staticalize runs, and staticalize automatically discovers them via og:image meta tag content URLs.
commit: |
The code is self-documenting; inline comments were redundant. Reduces file from ~230 lines to ~170 lines.
cowboyd
left a comment
There was a problem hiding this comment.
I'd try lazy generation first. It will be a less invasive change, and it means the website will work the same in local context as everywhere (no need to have a separate task at build time)
It also means that if generation isn't working, it will be caught during dev time before CI even runs
|
If there is principle here to extract, it is that what makes staticalize work is that the there is no build, only capture. The server is the build. |
Replace build-time PNG generation with on-demand rendering via Revolution plugin. PNGs are now generated when requested and cached using the Web Cache API. Changes: - Add www/plugins/og-image.ts - intercepts /blog/**/*.png requests - Use @effectionx/fs for Effection-native file operations - Remove www/scripts/generate-og-images.ts build script - Remove generate-og-images task from deno.json - Remove CI step for pre-generating images Benefits: - Server IS the build (cowboyd's principle) - Same behavior locally and in production - Issues caught during dev before CI runs - No separate build step needed
The PNG is generated on-demand by the og-image plugin, so we check if the source SVG exists instead of the output PNG.
- Use @effectionx/fetch instead of native fetch for WASM download - Make route detection generic (check if SVG exists instead of /blog/ prefix) - Make transformSvg stateless using reduce pipeline - Add documentation comment explaining transformSvg purpose - Make stripAnimations use local variable instead of reassigning parameter
| console.error(`OG image generation failed for ${pathname}:`, error); | ||
| return yield* serveFallback(request, options.basedir); |
There was a problem hiding this comment.
I'm not sure if this should be recoverable. It means we found an svg that should be rendered off of the png but that it failed, which means that there is a bug in our site.
| let AppHtml = yield* useAppHtml({ | ||
| title: `${post.title} | Blog | Effection`, | ||
| description: post.description, | ||
| ogImage: post.ogImage ? `/blog/${id}/${post.ogImage}` : undefined, |
There was a problem hiding this comment.
this is where the fallback should go, not a const. This is detecting that there is actually no image associated with the blog post.
| // Compute OG image path: if SVG exists, the PNG will be generated on-demand | ||
| // Note: blog-post-route.tsx prepends /blog/{id}/ to this value | ||
| let ogImage: string | undefined; | ||
| if (frontmatter.image?.endsWith(".svg")) { | ||
| let svgFullPath = `${directory}${id}/${frontmatter.image}`; | ||
| if (existsSync(svgFullPath)) { | ||
| ogImage = frontmatter.image.replace(/\.svg$/, ".png"); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
This bleeds the plugin logic into the blog post, while at the same time duplicating it. not good.
The plugin should transform the html and do the check there. You could probably generate it at that point to, so the plugin is not an *http() plugin, but an *html().
|
This has been implemented. |
Motivation
Social media platforms (Twitter, LinkedIn, Facebook, etc.) don't support SVG images in Open Graph
og:imagemeta tags. Our blog posts use SVG featured images that render beautifully on the blog but appear broken or missing when shared on social media.This PR adds build-time PNG generation from SVG featured images so social previews work correctly.
Approach
!importantrules at end of SVG) to ensure consistent light mode colors regardless of system themeBuild & Deploy Flow
Result
When a blog post is shared on social media, the platform fetches the
og:imageURL and displays the PNG thumbnail instead of showing a broken image or generic fallback.