import { ImageResponse } from 'next/og'; import { fetchStoreInfo } from '@/core/lib/brainerce.server'; import { resolveStoreName } from '@/core/lib/store-name'; /** * Default Open Graph card for every page that doesn't set its own og:image * (home, /products, /faq, /contact, content pages without an OG image). * Product / category / blog pages override this with their real images. * * Without this file, any page lacking an image gets NO social card at all — * a bare-domain link in WhatsApp/Facebook/X renders as plain text. */ export const size = { width: 1200, height: 630 }; export const contentType = 'image/png'; export const alt = 'Store'; /** * Satori (the OG renderer) ships a Latin-only default font — a Hebrew or * Arabic store name would render as tofu (□□□). Fetch a Noto Sans variant * with the needed coverage at request time; fetching the CSS without a * browser UA makes Google Fonts return TTF (which Satori accepts — it can't * parse woff2). Cached by the framework; on any failure we render without * text rather than shipping tofu. */ async function loadFont(text: string): Promise { try { const css = await fetch( `https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@700&family=Noto+Sans:wght@700&display=swap&text=${encodeURIComponent(text)}`, { cache: 'force-cache' } ).then((r) => r.text()); const url = css.match(/src:\s*url\((.+?)\)\s*format\(['"](?:truetype|opentype)['"]\)/)?.[1]; if (!url) return null; return await fetch(url, { cache: 'force-cache' }).then((r) => r.arrayBuffer()); } catch { return null; } } // Basic Latin through Latin Extended-B (U+0020–U+024F) plus whitespace, // punctuation, and digits — the coverage of Satori's bundled default font. const LATIN_ONLY = /^[ -ɏ\s\p{P}\p{N}]*$/u; export default async function OpenGraphImage() { const info = await fetchStoreInfo(); // Live name first, then the build-time NEXT_PUBLIC_STORE_NAME, then the // scaffold literal — the card must never read a stale baked name while the // next to it reads the live one. const name = resolveStoreName(info); const description = info?.metaDescription?.trim() || ''; const logo = info?.logo || null; const text = `${name} ${description}`.trim(); const font = text ? await loadFont(text) : null; // Default font covers Latin only — if the font fetch failed and the text // needs more coverage, drop the text instead of rendering tofu. const canRenderText = Boolean(text) && (font !== null || LATIN_ONLY.test(text)); return new ImageResponse( <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 32, background: 'linear-gradient(135deg, #111113 0%, #1c1c22 55%, #26262e 100%)', color: '#fafafa', }} > {logo ? ( <img src={logo} alt="" style={{ maxHeight: 220, maxWidth: 520, objectFit: 'contain' }} /> ) : null} {canRenderText && name ? ( <div style={{ display: 'flex', fontSize: logo ? 56 : 84, fontWeight: 700, textAlign: 'center', maxWidth: 1000, }} > {name} </div> ) : null} {canRenderText && description ? ( <div style={{ display: 'flex', fontSize: 32, color: '#a1a1aa', textAlign: 'center', maxWidth: 960, }} > {description.length > 120 ? `${description.slice(0, 119)}…` : description} </div> ) : null} </div>, { ...size, ...(font ? { fonts: [ { name: 'Noto Sans', data: font, weight: 700 as const, style: 'normal' as const }, ], } : {}), } ); }