Skip to content

Commit e42501b

Browse files
seanghayclaude
andcommitted
docs: inline OG font as base64 so Vercel function can read it
The previous code resolved the .ttf path via __dirname/path.resolve, which Vite cannot statically analyze, so the font was never bundled with the SSR build. Switching to ?url emitted the asset, but React Router moves emitted assets to the client-only build — the function cannot fs.readFile a client static asset on Vercel. Use ?inline to embed the font as a data URL inside the SSR bundle, decode at first call, and write to /tmp so skia-canvas can load it via path. Also surface font-load errors instead of swallowing them, which is what made the failure look like a blank white image instead of a 500. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 90d1084 commit e42501b

1 file changed

Lines changed: 17 additions & 14 deletions

File tree

docs/app/lib/og.server.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
// Generates Open Graph images at request time using Sone itself.
22
// Used by /og/docs/*.jpg to produce per-page social-share cards.
33

4+
import { writeFile } from 'node:fs/promises';
5+
import { tmpdir } from 'node:os';
46
import path from 'node:path';
5-
import { fileURLToPath } from 'node:url';
67
import { Column, Font, Row, Span, Text, sone } from 'sone';
8+
// `?inline` embeds the font as a base64 data URL in the SSR bundle so the
9+
// bytes ship inside the Vercel function (no separate asset file needed).
10+
// React Router otherwise moves emitted assets to the client-only build,
11+
// where the function cannot read them at runtime.
12+
import fontDataUrl from '../fonts/GoogleSans-Variable.ttf?inline';
713

814
const FG = '#0a0a0a';
915
const FG_MUTED = '#525252';
1016
const BORDER = '#e5e5e5';
1117

12-
// Load Google Sans once at module init. The font ships in the repo at
13-
// app/fonts/ so it's available in any deploy environment that bundles the
14-
// app's `app/` tree (Vercel functions include it because we reference it
15-
// from server code).
16-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
17-
const FONT_PATH = path.resolve(__dirname, '..', 'fonts', 'GoogleSans-Variable.ttf');
18-
1918
let fontReady: Promise<void> | undefined;
2019
async function ensureFonts(): Promise<void> {
2120
if (fontReady) return fontReady;
2221
fontReady = (async () => {
23-
if (!Font.has('GoogleSans')) {
24-
try {
25-
await Font.load('GoogleSans', FONT_PATH);
26-
} catch (err) {
27-
console.warn('[og] could not load GoogleSans:', err);
28-
}
22+
if (Font.has('GoogleSans')) return;
23+
const base64 = fontDataUrl.split(',', 2)[1];
24+
const bytes = Buffer.from(base64, 'base64');
25+
const fontPath = path.join(tmpdir(), 'sone-google-sans.ttf');
26+
await writeFile(fontPath, bytes);
27+
try {
28+
await Font.load('GoogleSans', fontPath);
29+
} catch (err) {
30+
console.error('[og] could not load GoogleSans:', err);
31+
throw err;
2932
}
3033
})();
3134
return fontReady;

0 commit comments

Comments
 (0)