diff --git a/api/app.ts b/api/app.ts
index 5ffb226..0e08808 100644
--- a/api/app.ts
+++ b/api/app.ts
@@ -395,18 +395,20 @@ export function createApp(config: AppConfig = {}) {
// Escape `<` so `` in report markdown can't break out.
const payload = JSON.stringify(diagnostic).replace(/window.__PUBLIC_DIAGNOSTIC__=${payload};`;
- // Replace generic title then inject SEO tags + payload before .
- const htmlWithTitle = html.replace(
- /
[^<]*<\/title>/,
- `${pageTitle}`,
- );
- const base = htmlWithTitle.includes("") ? htmlWithTitle : html;
- const withPayload = base.includes("")
- ? base.replace(
+ // Strip the static SEO tags from index.html so crawlers (which
+ // pick the first occurrence) don't read the generic defaults
+ // instead of our dynamic ones.
+ const stripped = html
+ .replace(/[^<]*<\/title>\s*/i, "")
+ .replace(/]*\/?>\s*/gi, "")
+ .replace(/]*\/?>\s*/gi, "")
+ .replace(/]*\/?>\s*/gi, "");
+ const withPayload = stripped.includes("")
+ ? stripped.replace(
"",
`\t\t${seoTags}\n\t\t${injected}\n\t`,
)
- : `${injected}${base}`;
+ : `${injected}${stripped}`;
return new Response(withPayload, {
headers: {
"content-type": "text/html; charset=utf-8",
diff --git a/api/assets/Inter-Bold.ttf b/api/assets/Inter-Bold.ttf
deleted file mode 100644
index 15e908f..0000000
Binary files a/api/assets/Inter-Bold.ttf and /dev/null differ
diff --git a/api/assets/Inter-Regular.ttf b/api/assets/Inter-Regular.ttf
deleted file mode 100644
index c544be4..0000000
Binary files a/api/assets/Inter-Regular.ttf and /dev/null differ
diff --git a/api/lib/og-image.tsx b/api/lib/og-image.tsx
index 1bfdde5..6ba337e 100644
--- a/api/lib/og-image.tsx
+++ b/api/lib/og-image.tsx
@@ -1,36 +1,43 @@
-import { readFile } from "node:fs/promises";
-import { join } from "node:path";
import { initWasm, Resvg } from "@resvg/resvg-wasm";
import satori from "satori";
import type { Diagnostic } from "../types/diagnostic.ts";
// ── Singletons initialised once per process ──────────────────────────────────
+// Fetched from CDN once and held in memory. Works in both Bun (local dev) and
+// Cloudflare Workers (production) — no filesystem required.
-let resvgReady = false;
+const RESVG_WASM_URL =
+ "https://unpkg.com/@resvg/resvg-wasm@2.6.2/index_bg.wasm";
+const INTER_REGULAR_URL =
+ "https://github.com/rsms/inter/raw/v4.0/extras/ttf/Inter-Regular.ttf";
+const INTER_BOLD_URL =
+ "https://github.com/rsms/inter/raw/v4.0/extras/ttf/Inter-Bold.ttf";
+
+let initPromise: Promise | null = null;
let fontRegular: ArrayBuffer | null = null;
let fontBold: ArrayBuffer | null = null;
-async function init() {
- if (resvgReady && fontRegular && fontBold) return;
-
- const assetsDir = join(import.meta.dir, "../assets");
- const [wasmBuf, reg, bold] = await Promise.all([
- readFile(
- join(
- import.meta.dir,
- "../../node_modules/@resvg/resvg-wasm/index_bg.wasm",
- ),
- ),
- readFile(join(assetsDir, "Inter-Regular.ttf")),
- readFile(join(assetsDir, "Inter-Bold.ttf")),
- ]);
-
- if (!resvgReady) {
- await initWasm(wasmBuf.buffer as ArrayBuffer);
- resvgReady = true;
+async function fetchBytes(url: string): Promise {
+ const res = await fetch(url);
+ if (!res.ok) {
+ throw new Error(`Failed to fetch ${url}: ${res.status}`);
}
- fontRegular = reg.buffer as ArrayBuffer;
- fontBold = bold.buffer as ArrayBuffer;
+ return res.arrayBuffer();
+}
+
+function init(): Promise {
+ if (initPromise) return initPromise;
+ initPromise = (async () => {
+ const [wasm, reg, bold] = await Promise.all([
+ fetchBytes(RESVG_WASM_URL),
+ fetchBytes(INTER_REGULAR_URL),
+ fetchBytes(INTER_BOLD_URL),
+ ]);
+ await initWasm(wasm);
+ fontRegular = reg;
+ fontBold = bold;
+ })();
+ return initPromise;
}
// ── Helpers ───────────────────────────────────────────────────────────────────