Skip to content

Commit 2a8a5ac

Browse files
Miriadseniordeveloper
andcommitted
debug: await ImageResponse body to surface WASM/font errors
ImageResponse uses ReadableStream internally which swallows errors. This version reads the body with arrayBuffer(), checks for empty response, and returns JSON diagnostics including font data types and sizes. Will reveal whether rawFonts plugin output is compatible with workers-og's satori/resvg-wasm pipeline. Co-authored-by: seniordeveloper <seniordeveloper@miriad.systems>
1 parent 7483e46 commit 2a8a5ac

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

apps/web/src/pages/api/og/default.png.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
/**
22
* Default OG image generation for generic pages.
3-
*
4-
* Uses workers-og (Satori + resvg-wasm) to generate 1200x630 PNG images
5-
* on Cloudflare Workers. Simpler layout — just branding + title + optional subtitle.
6-
* Used for homepage, author pages, and other generic pages.
7-
*
8-
* Usage: /api/og/default.png?title=About+Us&subtitle=Learn+more+about+CodingCat.dev
9-
*
10-
* Query params:
11-
* - title (required): Page title
12-
* - subtitle (optional): Subtitle text
3+
*
4+
* Debug version: bypasses ImageResponse's ReadableStream to surface errors.
5+
* Uses workers-og's ImageResponse but reads the body to catch stream errors.
136
*/
147
import type { APIRoute } from "astro";
158
import { ImageResponse } from "workers-og";
@@ -23,16 +16,54 @@ export const GET: APIRoute = async ({ url }) => {
2316
const subtitle = url.searchParams.get("subtitle") || undefined;
2417

2518
const html = generateDefaultOgHtml({ title, subtitle });
19+
const fonts = loadFonts();
2620

27-
const response = new ImageResponse(html, {
21+
// Debug: log font data types to understand what rawFonts plugin produces
22+
const fontDebug = fonts.map((f: any) => ({
23+
name: f.name,
24+
weight: f.weight,
25+
dataType: typeof f.data,
26+
isArrayBuffer: f.data instanceof ArrayBuffer,
27+
isUint8Array: f.data instanceof Uint8Array,
28+
dataLength: f.data?.byteLength || f.data?.length || 0,
29+
constructor: f.data?.constructor?.name,
30+
}));
31+
32+
// Create ImageResponse — this returns immediately with a ReadableStream body
33+
const ogResponse = new ImageResponse(html, {
2834
width: 1200,
2935
height: 630,
30-
fonts: loadFonts(),
36+
fonts,
3137
});
3238

33-
response.headers.set("Cache-Control", OG_CACHE_HEADER);
39+
// Read the body to force the stream to execute (and catch errors)
40+
const body = await ogResponse.arrayBuffer();
41+
42+
if (body.byteLength === 0) {
43+
// Stream produced no data — WASM or font error was swallowed
44+
return new Response(
45+
JSON.stringify({
46+
error: "ImageResponse produced empty body",
47+
fontDebug,
48+
htmlLength: html.length,
49+
hint: "WASM init or font loading likely failed silently inside ReadableStream",
50+
}),
51+
{
52+
status: 500,
53+
headers: { "Content-Type": "application/json" },
54+
}
55+
);
56+
}
3457

35-
return response;
58+
// Success — return the PNG with proper headers
59+
return new Response(body, {
60+
status: 200,
61+
headers: {
62+
"Content-Type": "image/png",
63+
"Content-Length": body.byteLength.toString(),
64+
"Cache-Control": OG_CACHE_HEADER,
65+
},
66+
});
3667
} catch (error: any) {
3768
return new Response(
3869
JSON.stringify({

0 commit comments

Comments
 (0)