feat(storefront): render branded OG images as PNG#255

open2
main
feat/better-og-images
openedJul 27, 2026byAAviddaram
View on GitHubTwo-way sync enabledPR last imported 19d ago

Summary

  • replace handwritten SVG social cards with Satori templates rendered to PNG
  • add branded home and product OG layouts with embedded product imagery
  • cache generated PNG responses and include a local preview command

Preview locally

sh
1cd apps/storev2
2bun run preview:og

Outputs: tmp/og-preview/home.png and tmp/og-preview/product.png.

Checks

  • bun run check-types
  • bun run build
  • bunx biome check on changed TypeScript files
  • bun run preview:og

Build completed. Existing Astro hints and transient static-navigation API warnings remain.

Summary by CodeRabbit

  • New Features
    • Added dynamically generated PNG Open Graph images for the storefront and product pages.
    • Product share images now include product details, pricing, branding, categories, and imagery.
    • Updated default social-sharing images to use PNG format for improved compatibility.
    • Added a preview command for generating sample Open Graph images locally.

Checks

4 successful Β· 1 skipped
All checks passed
ec80a0d

Added 1 commit

  • feat(storefront): render OG images as PNG7203205
Synced from GitHub

Review Change Stack

Important

  • πŸ” Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

βš™οΈ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bb5fd1d-0517-4320-80e0-7b987770dbb7

πŸ“ Walkthrough

Walkthrough

Changes

Dynamic OG PNG generation

Layer / File(s)Summary
OG layout renderers
apps/storev2/src/lib/og-image.ts
Adds shared OG dimensions, font and product data types, styling, and Satori renderers for home and product SVG layouts.
PNG response and route flow
apps/storev2/src/lib/og-response.ts, apps/storev2/src/pages/og.png.ts, apps/storev2/src/pages/og/products/[slug].png.ts
Loads fonts and Resvg WASM, converts SVG to PNG responses, and adds home and product OG endpoints with product lookup and image embedding.
SEO and preview integration
apps/storev2/src/components/SEO.astro, apps/storev2/src/pages/products/[slug].astro, apps/storev2/package.json, apps/storev2/scripts/preview-og-images.ts
Switches default and product SEO image URLs to PNG endpoints and adds a local OG preview command with new rendering dependencies.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant Browser
3 participant ProductPage
4 participant ProductOgRoute
5 participant ProductAPI
6 participant OgRenderer
7 participant Resvg
8 Browser->>ProductPage: requests product page
9 ProductPage-->>Browser: provides PNG OG image URL
10 Browser->>ProductOgRoute: requests product OG PNG
11 ProductOgRoute->>ProductAPI: fetches product data
12 ProductOgRoute->>OgRenderer: renders product SVG
13 ProductOgRoute->>Resvg: converts SVG to PNG
14 Resvg-->>Browser: returns image/png response
πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check nameStatusExplanation
Description Checkβœ… PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title checkβœ… PassedThe title clearly matches the main change: replacing OG image generation with branded PNG output.
Linked Issues checkβœ… PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkβœ… PassedCheck skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

Cchatgpt-codex-connectorBotreviewed Β· 1 inline threadJul 27, 2026
Synced from GitHub

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed6216ca3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

apps/storev2/src/lib/og-response.tsline 34Outdated Β· ed6216cOpen thread
……@@ -0,0 +1,55 @@
31+};
32+
33+const ensureWasm = (requestUrl: string) => {
34+ wasmReady ??= initWasm(fetch(new URL("/og-assets/resvg.wasm", requestUrl)));

P1 Badge Import resvg Wasm as a build-time module

Because apps/storev2/alchemy.run.ts:25-41 deploys this handler as a Cloudflare Worker, passing a fetched Response to the wasm-bindgen initializer makes it dynamically compile the downloaded bytes; the Workers runtime only supports instantiation from a build-time-imported WebAssembly.Module. Consequently both new PNG endpoints fail during ensureWasm() in production even though the local preview and build succeed. Import the package's Wasm file as a module and pass that module to initWasm instead.

Useful? React with πŸ‘Β / πŸ‘Ž.

Added 1 commit

  • fix(storefront): improve OG thumbnail legibilityec80a0d
CcoderabbitaiBotreviewed Β· 4 inline threadsJul 27, 2026
Synced from GitHub

Actionable comments posted: 4

🧹 Nitpick comments (2)
apps/storev2/scripts/preview-og-images.ts (2)

29-31: 🎯 Functional Correctness | πŸ”΅ Trivial | πŸ—οΈ Heavy lift

Preview renders with sharp/librsvg while production renders with resvg β€” the PNGs can differ.

The two rasterizers diverge on text shaping, gradients, border-radius, and embedded raster scaling, so a preview that looks correct is not evidence the deployed /og.png is. Consider driving the preview through the same @resvg/resvg-wasm path used by svgToPngResponse (reading the wasm from disk) so the preview is authoritative.

Also applies to: 54-56

πŸ€– Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@apps/storev2/scripts/preview-og-images.ts` around lines 29 - 31, Update the
5preview generation flow in preview-og-images.ts to rasterize SVGs with the same
6`@resvg/resvg-wasm` implementation used by svgToPngResponse instead of sharp. Load
7the Resvg WASM from disk, render both the home and alternate OG SVG outputs
8through that shared path, and write the resulting PNG buffers to the existing
9output files.

33-42: 🩺 Stability & Availability | πŸ”΅ Trivial | ⚑ Quick win

Hardcoded remote sample makes the preview fail offline or when the asset is removed.

A network failure here aborts the whole script after the home preview was already written, leaving a half-populated output dir. Catch the fetch failure and render the no-image product layout instead β€” that also exercises the fallback branch.

♻️ Suggested change
diff
1-const imageResponse = await fetch(productImageUrl);
2-if (!imageResponse.ok)
3- throw new Error("Could not load the sample product image");
4-const imageType = imageResponse.headers.get("content-type") || "image/webp";
5-const imageData = Buffer.from(await imageResponse.arrayBuffer()).toString(
6- "base64",
7-);
8+let sampleImage: string | null = null;
9+try {
10+ const imageResponse = await fetch(productImageUrl);
11+ if (imageResponse.ok) {
12+ const imageType =
13+ imageResponse.headers.get("content-type") || "image/webp";
14+ const imageData = Buffer.from(
15+ await imageResponse.arrayBuffer(),
16+ ).toString("base64");
17+ sampleImage = `data:${imageType};base64,${imageData}`;
18+ } else {
19+ console.warn("Sample product image unavailable; rendering fallback");
20+ }
21+} catch {
22+ console.warn("Sample product image fetch failed; rendering fallback");
23+}

and pass imageUrl: sampleImage at Line 50.

πŸ€– Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@apps/storev2/scripts/preview-og-images.ts` around lines 33 - 42, Update the
5sample product image flow around sourceImage and imageResponse to catch fetch or
6image-processing failures, then render the product preview using the no-image
7fallback instead of aborting after the home preview is written. Ensure the
8product rendering call uses the fallback image URL (sampleImage) when the remote
9asset is unavailable, while preserving the normal fetched-image path when
10successful.
πŸ€– Prompt for all review comments with AI agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4Inline comments:
5In `@apps/storev2/src/lib/og-response.ts`:
6- Around line 18-36: Update loadOgFonts and ensureWasm so cached initialization
7promises are cleared when they reject, allowing later OG requests to retry after
8transient font or WASM asset failures. Preserve successful promise caching and
9the existing initialization behavior, and apply the rejection reset to both
10fontsReady and wasmReady.
11- Around line 38-55: Update svgToPngResponse so renderer cleanup is guaranteed
12with a finally block covering rendering and PNG conversion, while preserving the
13existing response behavior. Pass the Uint8Array returned by rendered.asPng()
14directly to Response instead of creating a copied array with Uint8Array.from.
15- Around line 33-36: Update ensureWasm to use the bundler-imported resvg WASM
16module/binding instead of dynamically fetching /og-assets/resvg.wasm, and ensure
17initialization is invoked only once. Preserve and return the same promise for
18subsequent calls so retries cannot call initWasm again after initialization has
19started.
20
21In `@apps/storev2/src/pages/og/products/`[slug].png.ts:
22- Around line 11-21: Update getImageUrl to request the CDN’s supported explicit
23PNG output format instead of replacing format=auto with the undocumented
24format=png value. Ensure the returned URL is configured for PNG generation while
25preserving the existing primary-image selection and absolute URL handling.
26
27---
28
29Nitpick comments:
30In `@apps/storev2/scripts/preview-og-images.ts`:
31- Around line 29-31: Update the preview generation flow in preview-og-images.ts
32to rasterize SVGs with the same `@resvg/resvg-wasm` implementation used by
33svgToPngResponse instead of sharp. Load the Resvg WASM from disk, render both
34the home and alternate OG SVG outputs through that shared path, and write the
35resulting PNG buffers to the existing output files.
36- Around line 33-42: Update the sample product image flow around sourceImage and
37imageResponse to catch fetch or image-processing failures, then render the
38product preview using the no-image fallback instead of aborting after the home
39preview is written. Ensure the product rendering call uses the fallback image
40URL (sampleImage) when the remote asset is unavailable, while preserving the
41normal fetched-image path when successful.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 72aa0d09-4499-4914-8d22-df0ae08ae5ed

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 70029a87edb0893ea30dd737898c00d57617c9cb and ed6216ca3ff87394852bc7662bbec69200d1e8e8.

β›” Files ignored due to path filters (7)
  • apps/storev2/public/og-assets/noto-sans-bold.ttf is excluded by !**/*.ttf
  • apps/storev2/public/og-assets/noto-sans-regular.ttf is excluded by !**/*.ttf
  • apps/storev2/public/og-assets/resvg.wasm is excluded by !**/*.wasm
  • apps/storev2/public/og-assets/unbounded-bold.ttf is excluded by !**/*.ttf
  • apps/storev2/public/og-assets/unbounded-semibold.ttf is excluded by !**/*.ttf
  • apps/storev2/public/og-image.svg is excluded by !**/*.svg
  • bun.lock is excluded by !**/*.lock
πŸ“’ Files selected for processing (9)
  • apps/storev2/package.json
  • apps/storev2/scripts/preview-og-images.ts
  • apps/storev2/src/components/SEO.astro
  • apps/storev2/src/lib/og-image.ts
  • apps/storev2/src/lib/og-response.ts
  • apps/storev2/src/pages/og.png.ts
  • apps/storev2/src/pages/og/products/[slug].png.ts
  • apps/storev2/src/pages/og/products/[slug].svg.ts
  • apps/storev2/src/pages/products/[slug].astro
πŸ’€ Files with no reviewable changes (1)
  • apps/storev2/src/pages/og/products/[slug].svg.ts
apps/storev2/src/lib/og-response.tslines 33–36Outdated Β· ed6216cOpen thread
……@@ -0,0 +1,55 @@
33+const ensureWasm = (requestUrl: string) => {
34+ wasmReady ??= initWasm(fetch(new URL("/og-assets/resvg.wasm", requestUrl)));
35+ return wasmReady;
36+};
CcoderabbitaiBoton lines 33–36Jul 27, 2026

🩺 Stability & Availability | 🟠 Major | πŸ—οΈ Heavy lift

🧩 Analysis chain

🏁 Script executed:

shell
1#!/bin/bash
2fd . --iglob '**/public/og-assets/*' -x ls -la {}

Repository: darjss/vit-store

Length of output: 339


🏁 Script executed:

shell
1#!/bin/bash
2set -u
3
4echo "== package/package manager files =="
5git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|wrangler\.toml|next\.config\.(js|mjs|ts)|vite\.config\.(js|mjs|ts)|astro\.config\.(js|mjs|ts))$' || true
6
7echo
8echo "== target file =="
9fd -a 'og-response\.ts$' . | while read -r f; do
10 echo "-- $f --"
11 wc -l "$f"
12 cat -n "$f"
13done
14
15echo
16echo "== public og-assets paths =="
17python3 - <<'PY'
18import os, pathlib
19roots=[p for p in pathlib.Path('.').rglob('public') if p.is_dir()]
20for r in roots:
21 pth=pathlib.Path(r)/'og-assets'
22 if pth.exists():
23 print(f'FOUND {pth}')
24 for root, dirs, files in os.walk(pth):
25 for f in sorted(files):
26 print(os.path.join(root, f))
27if not any(p.is_dir() for p in pathlib.Path('.').rglob('public')):
28 print('NO public directories found')
29PY
30
31echo
32echo "== search for resvg / wasm / og-assets usage =="
33rg -n "og-assets|resvg|initWasm|wasmb|wasmReady|Already initialized" -S . || true

Repository: darjss/vit-store

Length of output: 4124


🏁 Script executed:

shell
1#!/bin/bash
2set -u
3
4echo "== lockfile package entry =="
5python3 - <<'PY'
6from pathlib import Path
7p=Path('bun.lock')
8text=p.read_text()
9start=text.find('"`@resvg/resvg-wasm`@2.6.2"')
10end=text.find('\n }', start)
11print(start, end)
12print(text[start:start+1200])
13PY
14
15echo
16echo "== installed module source if available =="
17python3 - <<'PY'
18from pathlib import Path
19for root in [Path('./node_modules'), Path('./.bun')]:
20 if root.exists():
21 for p in root.rglob('*'):
22 if p.is_file() and p.name in {'index.js','module.js','index.d.ts'} and 'resvg' in ('/'.join(p.parts), str(p)):
23 print(f'-- {p} --')
24 text=p.read_text(errors='ignore').splitlines()
25 for i,line in enumerate(text[:80], 1):
26 if 'initWasm' in line or 'Already initialized' in line or 'WebAssembly' in line or 'response' in line.lower():
27 print(f'{i}: {line}')
28PY
29
30echo
31echo "== all OgAsset references =="
32rg -n "OgAsset|og-assets|resvg\.wasm|initWasm|new Response\(|cache" -S apps/storev2/src apps/storev2/public apps/storev2/astro.config.mjs apps/storev2/package.json || true

Repository: darjss/vit-store

Length of output: 3846


🌐 Web query:

@resvg/resvg-wasm initWasm Already initialized repeated calls

πŸ’‘ Result:

The error "Already initialized. The initWasm() function can be used only once" occurs in @resvg/resvg-wasm because the library's internal state tracking explicitly prevents the initWasm() function from being called more than once [1][2]. This behavior is standard in the library's design to prevent re-initialization of the WebAssembly module, which is unnecessary and can cause issues [1][2]. In environments like serverless functions (e.g., Cloudflare Workers) or hot-reloading development environments, code execution may re-trigger the initialization logic, leading to this error [3][4][5]. To resolve or prevent this, you should ensure that initWasm() is called only once in the application's lifecycle, or implement a guard to handle repeated calls gracefully. Recommended approaches: 1. Use a local variable to guard the initialization: Create a persistent flag to track whether the module has been initialized before calling initWasm() [5]. let initialized = false; export async function initializeResvg { if (initialized) return; await initWasm(wasmModule); initialized = true; } 2. Handle the error explicitly: If you cannot easily control the initialization flow, you can catch the error and ignore it if it specifically indicates the module is already initialized [4][5]. try { await initWasm(wasmModule); } catch (e) { if (!(e instanceof Error && e.message.includes('Already initialized'))) { throw e; } } Prioritize the first approach, as it is cleaner and avoids unnecessary promise overhead [5].

Citations:


🌐 Web query:

@resvg/resvg-wasm initWasm fetch response Cloudflare Workers fetch WASM

πŸ’‘ Result:

Using @resvg/resvg-wasm in Cloudflare Workers requires avoiding runtime dynamic fetching of the WASM binary, as the Workers runtime restricts dynamic WebAssembly compilation [1][2]. Instead, you must use static imports to allow the Wrangler bundler to pre-compile the WASM module [1][3]. Recommended Approach: 1. Static Import: Import the WASM file directly at the top of your file [3][4][5]. This allows Wrangler to handle the binary inclusion during the build process [1][3]. import resvgWasm from '@resvg/resvg-wasm/index_bg.wasm'; 2. Initialization: Pass this imported module directly to initWasm [1][4][5]. Since initWasm can only be called once, use a guard to ensure it executes only once per Worker instance [4][5][6]. Example Pattern: import { Resvg, initWasm } from '@resvg/resvg-wasm'; import resvgWasm from '@resvg/resvg-wasm/index_bg.wasm'; let initialized = false; async function ensureWasmInitialized { if (initialized) return; await initWasm(resvgWasm); initialized = true; } // Inside your fetch handler export default { async fetch(request, env, ctx) { await ensureWasmInitialized; // Proceed with Resvg usage } }; Avoid using fetch to load the.wasm file at runtime, as this will trigger a CompileError: WebAssembly.instantiate: Wasm code generation disallowed by embedder in the Cloudflare Workers environment [1][2]. If your build system (such as some Vite/SSR configurations) cannot handle direct.wasm imports, you may need to use a post-build script to patch the import into your worker bundle [1].

Citations:


Use a bundler-imported WASM module and guard the single init path.

/og-assets/resvg.wasm exists, but wasmReady ??= initWasm(fetch(...)) can still call initWasm() on retry when initialization has already started, which @resvg/resvg-wasm will reject as Already initialized. Keep a separate runtime-initialized flag and pass the same error-bound promise repeatedly if you must keep the dynamic fetch(...) form, or a bundler-imported WASM module/binding if this runs under Cloudflare Workers.

πŸ€– Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@apps/storev2/src/lib/og-response.ts` around lines 33 - 36, Update ensureWasm
5to use the bundler-imported resvg WASM module/binding instead of dynamically
6fetching /og-assets/resvg.wasm, and ensure initialization is invoked only once.
7Preserve and return the same promise for subsequent calls so retries cannot call
8initWasm again after initialization has started.
apps/storev2/src/lib/og-response.tslines 18–36Outdated Β· ed6216cOpen thread
……@@ -0,0 +1,55 @@
33+const ensureWasm = (requestUrl: string) => {
34+ wasmReady ??= initWasm(fetch(new URL("/og-assets/resvg.wasm", requestUrl)));
35+ return wasmReady;
36+};
CcoderabbitaiBoton lines 18–36Jul 27, 2026

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

Rejected promises are cached forever, poisoning the isolate.

fontsReady ??= / wasmReady ??= store the promise before it settles, so a single transient fetch failure (cold start, asset 5xx) permanently breaks every later OG request in that isolate β€” each one re-awaits the same rejected promise. Reset the cache on rejection.

πŸ›‘οΈ Proposed fix
diff
1 export const loadOgFonts = (requestUrl: string) => {
2- fontsReady ??= Promise.all([
3+ fontsReady ??= Promise.all([
4 fetchAsset(requestUrl, "/og-assets/noto-sans-regular.ttf"),
5 fetchAsset(requestUrl, "/og-assets/noto-sans-bold.ttf"),
6 fetchAsset(requestUrl, "/og-assets/unbounded-semibold.ttf"),
7 fetchAsset(requestUrl, "/og-assets/unbounded-bold.ttf"),
8- ]).then(([sansRegular, sansBold, unboundedSemibold, unboundedBold]) => ({
9- sansRegular,
10- sansBold,
11- unboundedSemibold,
12- unboundedBold,
13- }));
14+ ])
15+ .then(([sansRegular, sansBold, unboundedSemibold, unboundedBold]) => ({
16+ sansRegular,
17+ sansBold,
18+ unboundedSemibold,
19+ unboundedBold,
20+ }))
21+ .catch((error) => {
22+ fontsReady = undefined;
23+ throw error;
24+ });
25 return fontsReady;
26 };
27
28 const ensureWasm = (requestUrl: string) => {
29- wasmReady ??= initWasm(fetch(new URL("/og-assets/resvg.wasm", requestUrl)));
30+ wasmReady ??= initWasm(
31+ fetch(new URL("/og-assets/resvg.wasm", requestUrl)),
32+ ).catch((error) => {
33+ wasmReady = undefined;
34+ throw error;
35+ });
36 return wasmReady;
37 };
πŸ“ Committable suggestion

‼️ IMPORTANT Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

suggestion
1export const loadOgFonts = (requestUrl: string) => {
2 fontsReady ??= Promise.all([
3 fetchAsset(requestUrl, "/og-assets/noto-sans-regular.ttf"),
4 fetchAsset(requestUrl, "/og-assets/noto-sans-bold.ttf"),
5 fetchAsset(requestUrl, "/og-assets/unbounded-semibold.ttf"),
6 fetchAsset(requestUrl, "/og-assets/unbounded-bold.ttf"),
7 ])
8 .then(([sansRegular, sansBold, unboundedSemibold, unboundedBold]) => ({
9 sansRegular,
10 sansBold,
11 unboundedSemibold,
12 unboundedBold,
13 }))
14 .catch((error) => {
15 fontsReady = undefined;
16 throw error;
17 });
18 return fontsReady;
19};
20
21const ensureWasm = (requestUrl: string) => {
22 wasmReady ??= initWasm(
23 fetch(new URL("/og-assets/resvg.wasm", requestUrl)),
24 ).catch((error) => {
25 wasmReady = undefined;
26 throw error;
27 });
28 return wasmReady;
29};
πŸ€– Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@apps/storev2/src/lib/og-response.ts` around lines 18 - 36, Update loadOgFonts
5and ensureWasm so cached initialization promises are cleared when they reject,
6allowing later OG requests to retry after transient font or WASM asset failures.
7Preserve successful promise caching and the existing initialization behavior,
8and apply the rejection reset to both fontsReady and wasmReady.
apps/storev2/src/pages/og/products/[slug].png.tslines 11–21Outdated Β· ed6216cOpen thread
……@@ -0,0 +1,77 @@
18+ "format=auto",
19+ "format=png",
20+ );
21+};
CcoderabbitaiBoton lines 11–21Jul 27, 2026

🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

shell
1#!/bin/bash
2fd -t f 'image.ts' apps/storev2/src/lib --exec cat -n {}

Repository: darjss/vit-store

Length of output: 11721


🏁 Script executed:

shell
1#!/bin/bash
2set -euo pipefail
3
4echo "## Locate og product page and image helper usages"
5fd -t f '\[slug\]\.png\.ts$' apps/storev2/src/pages/og --exec sh -c 'echo "===== {} ====="; cat -n "{}"'
6
7echo
8echo "## Search toProductImageUrl usage"
9rg -n "toProductImageUrl|format=auto|format=png|getImageUrl|renderProductOgSvg" apps/storev2/src -S

Repository: darjss/vit-store

Length of output: 5030


🌐 Web query:

Cloudflare Images CDN-cgi image format parameter supports PNG JPEG WebP

πŸ’‘ Result:

Yes, the Cloudflare image transformation service (accessed via /cdn-cgi/image/ URLs) fully supports the format parameter for WebP and JPEG [1][2]. While PNG is a supported input format, the format parameter specifically controls the transcoded output format [1][3]. When specifying output formats, the service supports the following values: - auto: Automatically serves the most efficient format (WebP or AVIF) based on the browser's capabilities [1][2]. - webp: Transcodes the image to Google WebP format [1][4]. - jpeg: Transcodes the image into interlaced progressive JPEG format [1][5]. - baseline-jpeg: Transcodes the image into baseline sequential JPEG format [1][4]. - avif: Transcodes the image to AVIF, falling back to WebP or JPEG if necessary [1][6]. Although PNG is not listed as an explicit output option in the format parameter documentation, Cloudflare supports PNG as an input format and will preserve or transcode images as required by the transformation settings [3][6]. If the format parameter is omitted, Cloudflare typically defaults to standard formats like JPEG or WebP depending on browser support [2].

Citations:


🌐 Web query:

docs.cloudflare.com images format parameter supported values png

πŸ’‘ Result:

The Cloudflare Images format parameter (also available as f) does not include png as a direct, explicit transformation option in its standard list of values [1][2]. The officially documented values for the format parameter are: - auto: Automatically serves the most efficient format supported by the requesting browser [1][2]. - avif: Transcodes the image to AVIF (with fallback to WebP or JPEG if encoding is too slow) [1][2]. - webp: Transcodes the image to WebP [1][2]. - jpeg: Transcodes the image to interlaced progressive JPEG [1][2]. - baseline-jpeg: Transcodes the image to baseline sequential JPEG [1][2]. - json: Outputs metadata about the image in JSON format [1][2]. Although png is a supported input format and can be a supported output format under certain conditions (such as when format is not explicitly set, allowing the system to use a standard format like PNG or JPEG) [3][4], it is not an explicit value you can pass to the format parameter [1][2]. However, when generating images in PNG format, you can use the quality parameter to trigger the use of a PNG8 (palette) variant [1][2].

Citations:


Use the CDN format that supports explicit PNG output.

format=png is not a documented Cloudflare image format value here, so the current replace can leave the CDNs auto/WebP path intact. Avoid the produced URL as a PNG-only fix; use the supported output format for this OG image, or centralize the image helper’s format choice.

πŸ€– Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@apps/storev2/src/pages/og/products/`[slug].png.ts around lines 11 - 21,
5Update getImageUrl to request the CDN’s supported explicit PNG output format
6instead of replacing format=auto with the undocumented format=png value. Ensure
7the returned URL is configured for PNG generation while preserving the existing
8primary-image selection and absolute URL handling.
apps/storev2/src/lib/og-response.tslines 38–55Outdated Β· ed6216cOpen thread
……@@ -0,0 +1,55 @@
52+ "Cache-Control": CACHE_CONTROL,
53+ },
54+ });
55+};
CcoderabbitaiBoton lines 38–55Jul 27, 2026

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

Free the renderer in a finally, and skip the byte-by-byte copy.

If new Resvg(...) or render() throws (malformed SVG, unsupported feature), renderer.free() never runs and the wasm allocation leaks for the isolate's lifetime. Also Uint8Array.from(png) copies element-by-element; asPng() already returns a detached Uint8Array that can be handed to Response directly.

πŸ›‘οΈ Proposed fix
diff
1 export const svgToPngResponse = async (svg: string, requestUrl: string) => {
2 await ensureWasm(requestUrl);
3 const renderer = new Resvg(svg, {
4 fitTo: { mode: "original" },
5 background: "`#fffdf5`",
6 });
7- const rendered = renderer.render();
8- const png = rendered.asPng();
9- rendered.free();
10- renderer.free();
11+ let png: Uint8Array;
12+ try {
13+ const rendered = renderer.render();
14+ try {
15+ png = rendered.asPng();
16+ } finally {
17+ rendered.free();
18+ }
19+ } finally {
20+ renderer.free();
21+ }
22
23- return new Response(Uint8Array.from(png).buffer, {
24+ return new Response(png, {
25 headers: {
26 "Content-Type": "image/png",
27 "Cache-Control": CACHE_CONTROL,
28 },
29 });
30 };
πŸ€– Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@apps/storev2/src/lib/og-response.ts` around lines 38 - 55, Update
5svgToPngResponse so renderer cleanup is guaranteed with a finally block covering
6rendering and PNG conversion, while preserving the existing response behavior.
7Pass the Uint8Array returned by rendered.asPng() directly to Response instead of
8creating a copied array with Uint8Array.from.
Cchatgpt-codex-connectorBotreviewed Β· 1 inline threadJul 27, 2026
Synced from GitHub

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27b5f563ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

apps/storev2/src/lib/og-response.tsline 52Outdated Β· 27b5f56Open thread
……@@ -0,0 +1,55 @@
49+ return new Response(Uint8Array.from(png).buffer, {
50+ headers: {
51+ "Content-Type": "image/png",
52+ "Cache-Control": CACHE_CONTROL,

P2 Badge Exempt the OG routes from the no-store middleware

For requests to /og.png or /og/products/..., apps/storev2/src/middleware.ts:28-46 classifies the path as non-public, disables Astro caching, and overwrites this header with Cache-Control: no-store after the handler returns. Consequently every crawler request reruns the product query, asset/image fetches, Satori layout, and PNG rasterization instead of using the advertised one-day/browser and one-week/CDN cache; allow these OG routes through the public cache path or otherwise exempt them from that override.

Useful? React with πŸ‘Β / πŸ‘Ž.

Cchatgpt-codex-connectorBotreviewed Β· 1 inline threadAug 26, 2026
Synced from GitHub

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec80a0d0c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

……@@ -0,0 +1,77 @@
17+ return toProductImageUrl(absoluteUrl, "md").replace(
18+ "format=auto",
19+ "format=png",
20+ );
Cchatgpt-codex-connectorBoton lines 17–20Aug 26, 2026

P2 Badge Normalize pre-transformed product images before embedding

When a stored image URL is already a Cloudflare transformation such as .../cdn-cgi/image/format=webp/..., toProductImageUrl() returns it unchanged (src/lib/image.ts:60), so this replacement never produces PNG. fetchImageDataUrl() then embeds WebP in the SVG, but the bundled resvg Wasm supports only PNG, JPEG, and GIF; these valid product records therefore render OG cards without their product image or fail rasterization. Override the format for every pre-transformed URL rather than replacing only format=auto.

Useful? React with πŸ‘Β / πŸ‘Ž.