From f2d49939983c46cc36e022c031ff23892065f623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 18 Jul 2026 10:28:54 +0200 Subject: [PATCH 1/3] feat(deployment): enable logs and traces --- wrangler.jsonc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wrangler.jsonc b/wrangler.jsonc index 690bd26a..2a7b50df 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -20,6 +20,16 @@ }, "observability": { "enabled": true, + "logs": { + "enabled": true, + "invocation_logs": true + }, + "traces": { + "enabled": true, + "destinations": [ + "otel-live-collector" + ] + } }, "routes": [ { From 833b319e477011f9ae8eb2696fd9cf900d028821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 18 Jul 2026 10:44:45 +0200 Subject: [PATCH 2/3] fix: styling --- src/components/ApiDocs/ApiDocs.astro | 5 +++-- src/pages/api/[...path].astro | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/ApiDocs/ApiDocs.astro b/src/components/ApiDocs/ApiDocs.astro index f89dc43c..9da7ff03 100644 --- a/src/components/ApiDocs/ApiDocs.astro +++ b/src/components/ApiDocs/ApiDocs.astro @@ -3,12 +3,13 @@ import TopSections from "./TopSections.astro"; import TagDocs from "./TagDocs.astro"; export interface Props { + class?: string; tagSlug?: string; operationSlug?: string; topSection?: "introduction" | "sdks" | "authentication" | "errors"; } -const { tagSlug, operationSlug, topSection } = Astro.props; +const { class: className, tagSlug, operationSlug, topSection } = Astro.props; const target = tagSlug ? operationSlug ? `${tagSlug}/${operationSlug}` @@ -16,7 +17,7 @@ const target = tagSlug : topSection; --- -
+
{!tagSlug && } {tagSlug && } diff --git a/src/pages/api/[...path].astro b/src/pages/api/[...path].astro index 14475e15..332e6283 100644 --- a/src/pages/api/[...path].astro +++ b/src/pages/api/[...path].astro @@ -122,6 +122,7 @@ const canonicalPath = path ? `/api/${path}` : "/api"; /> - From 8c0f6ea10f2e71487c10fb853c6e84c6566c14f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 18 Jul 2026 10:59:47 +0200 Subject: [PATCH 3/3] chore: rework OG images Get rid of `workers-og` which has noisy logs and duplicate initialization errors. --- package-lock.json | 22 +--------- package.json | 3 +- src/env.d.ts | 5 +++ src/pages/og/[...slog].png.ts | 21 ++++++---- src/utils/ogImageResponse.ts | 76 +++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 28 deletions(-) create mode 100644 src/utils/ogImageResponse.ts diff --git a/package-lock.json b/package-lock.json index ef3913fd..6850708c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@eslint/js": "9.39.5", "@expressive-code/plugin-collapsible-sections": "^0.44.0", "@expressive-code/plugin-line-numbers": "0.44.0", + "@resvg/resvg-wasm": "2.4.0", "@sindresorhus/slugify": "^3.0.0", "@sumup-oss/circuit-ui": "11.12.0", "@sumup-oss/design-tokens": "10.1.0", @@ -49,6 +50,7 @@ "react-final-form": "^7.0.1", "rumdl": "0.2.34", "sass": "^1.101.0", + "satori": "0.15.2", "satteri": "0.9.5", "starlight-links-validator": "^0.25.2", "starlight-llms-txt": "0.11.0", @@ -56,7 +58,6 @@ "typescript": "^6.0.3", "typescript-eslint": "8.64.0", "vitest": "4.1.10", - "workers-og": "0.0.27", "wrangler": "4.110.0" }, "engines": { @@ -11320,13 +11321,6 @@ "node": ">=4.0" } }, - "node_modules/just-camel-case": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-camel-case/-/just-camel-case-6.2.0.tgz", - "integrity": "sha512-ICenRLXwkQYLk3UyvLQZ+uKuwFVJ3JHFYFn7F2782G2Mv2hW8WPePqgdhpnjGaqkYtSVWnyCESZhGXUmY3/bEg==", - "dev": true, - "license": "MIT" - }, "node_modules/katex": { "version": "0.16.25", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", @@ -17390,18 +17384,6 @@ "@cloudflare/workerd-windows-64": "1.20260714.1" } }, - "node_modules/workers-og": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/workers-og/-/workers-og-0.0.27.tgz", - "integrity": "sha512-QvwptQ0twmouQHiITUi3kYxEPCLdueC/U4msQ2xMz2iktd+iseSs7zlREw3T1dAsPxPw73FQlw8cXFsfANZPlw==", - "dev": true, - "dependencies": { - "@resvg/resvg-wasm": "2.4.0", - "just-camel-case": "^6.2.0", - "satori": "^0.15.2", - "yoga-wasm-web": "0.3.3" - } - }, "node_modules/wrangler": { "version": "4.110.0", "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.110.0.tgz", diff --git a/package.json b/package.json index 067a9c75..920ed951 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@eslint/js": "9.39.5", "@expressive-code/plugin-collapsible-sections": "^0.44.0", "@expressive-code/plugin-line-numbers": "0.44.0", + "@resvg/resvg-wasm": "2.4.0", "@sindresorhus/slugify": "^3.0.0", "@sumup-oss/circuit-ui": "11.12.0", "@sumup-oss/design-tokens": "10.1.0", @@ -95,6 +96,7 @@ "react-final-form": "^7.0.1", "rumdl": "0.2.34", "sass": "^1.101.0", + "satori": "0.15.2", "satteri": "0.9.5", "starlight-links-validator": "^0.25.2", "starlight-llms-txt": "0.11.0", @@ -102,7 +104,6 @@ "typescript": "^6.0.3", "typescript-eslint": "8.64.0", "vitest": "4.1.10", - "workers-og": "0.0.27", "wrangler": "4.110.0" } } diff --git a/src/env.d.ts b/src/env.d.ts index c4f58f0d..b9560e3b 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -12,3 +12,8 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +declare module "*.wasm" { + const module: WebAssembly.Module; + export default module; +} diff --git a/src/pages/og/[...slog].png.ts b/src/pages/og/[...slog].png.ts index a5140690..2c01a9a4 100644 --- a/src/pages/og/[...slog].png.ts +++ b/src/pages/og/[...slog].png.ts @@ -1,10 +1,10 @@ import { getCollection } from "astro:content"; import { createElement } from "react"; -import { ImageResponse } from "workers-og"; import SumUpBlackData from "../../assets/fonts/sumup-black-latin-s.ttf"; import SumUpNarrowMediumData from "../../assets/fonts/sumup-narrow-latin-s-medium.ttf"; import SumUpNarrowRegularData from "../../assets/fonts/sumup-narrow-latin-s-regular.ttf"; +import { createOgImageResponse } from "../../utils/ogImageResponse"; interface Props { params: { slog?: string }; @@ -57,6 +57,13 @@ function getOpenTypeSignature(data: Uint8Array) { return String.fromCharCode(...data.subarray(0, 4)); } +function toArrayBuffer(data: Uint8Array) { + return data.buffer.slice( + data.byteOffset, + data.byteOffset + data.byteLength, + ) as ArrayBuffer; +} + function toSupportedFontData(data: Uint8Array | string, label: string) { if (typeof data === "string") { console.warn( @@ -67,12 +74,12 @@ function toSupportedFontData(data: Uint8Array | string, label: string) { if (getOpenTypeSignature(data) === "wOF2") { console.warn( - `[og] ${label} uses WOFF2, which Satori/workers-og does not support. Falling back to the default font.`, + `[og] ${label} uses WOFF2, which Satori does not support. Falling back to the default font.`, ); return null; } - return data; + return toArrayBuffer(data); } function createFontStack(primary: string | null) { @@ -253,7 +260,7 @@ export async function GET({ params }: Props) { ), ); - return new ImageResponse(card, { + return createOgImageResponse(card, { width: 1200, height: 600, debug: false, @@ -264,7 +271,7 @@ export async function GET({ params }: Props) { name: "SumUp Black", data: sumUpBlack, style: "normal" as const, - weight: 700, + weight: 700 as const, }, ] : []), @@ -274,7 +281,7 @@ export async function GET({ params }: Props) { name: "SumUp Narrow", data: sumUpNarrowRegular, style: "normal" as const, - weight: 400, + weight: 400 as const, }, ] : []), @@ -284,7 +291,7 @@ export async function GET({ params }: Props) { name: "SumUp Narrow", data: sumUpNarrowMedium, style: "normal" as const, - weight: 500, + weight: 500 as const, }, ] : []), diff --git a/src/utils/ogImageResponse.ts b/src/utils/ogImageResponse.ts new file mode 100644 index 00000000..5b6d0fdb --- /dev/null +++ b/src/utils/ogImageResponse.ts @@ -0,0 +1,76 @@ +import { Resvg, initWasm } from "@resvg/resvg-wasm"; +import satori from "satori"; + +import resvgWasm from "@resvg/resvg-wasm/index_bg.wasm"; + +import type { ReactNode } from "react"; +import type { Font, SatoriOptions } from "satori"; + +type OgImageResponseOptions = { + width: number; + height: number; + debug?: boolean; + fonts: Font[]; + headers?: HeadersInit; + status?: number; + statusText?: string; +}; + +let resvgInitPromise: Promise | undefined; +let resvgInitialized = false; + +async function ensureResvg() { + if (resvgInitialized) { + return; + } + + resvgInitPromise ??= initWasm(resvgWasm).then( + () => { + resvgInitialized = true; + }, + (error: unknown) => { + resvgInitPromise = undefined; + throw error; + }, + ); + + await resvgInitPromise; +} + +export async function createOgImageResponse( + element: ReactNode, + options: OgImageResponseOptions, +) { + await ensureResvg(); + + const svg = await satori(element, { + width: options.width, + height: options.height, + debug: options.debug, + fonts: options.fonts, + } satisfies SatoriOptions); + const png = new Resvg(svg, { + fitTo: { + mode: "width", + value: options.width, + }, + }) + .render() + .asPng(); + const body = png.buffer.slice( + png.byteOffset, + png.byteOffset + png.byteLength, + ) as ArrayBuffer; + + return new Response(body, { + headers: { + "Content-Type": "image/png", + "Cache-Control": options.debug + ? "no-cache, no-store" + : "public, immutable, no-transform, max-age=31536000", + ...options.headers, + }, + status: options.status ?? 200, + statusText: options.statusText, + }); +}