Skip to content

fix: resolve OG image font loading ENOENT on Vercel#138

Open
julianbenegas wants to merge 2 commits intomainfrom
forums/fix-og-font-loading-ee5ca
Open

fix: resolve OG image font loading ENOENT on Vercel#138
julianbenegas wants to merge 2 commits intomainfrom
forums/fix-og-font-loading-ee5ca

Conversation

@julianbenegas
Copy link
Member

The OG image routes were using readFile with process.cwd() to load
GeistMono fonts from node_modules, which fails on Vercel deployments
because the fonts aren't included in the serverless function bundle
(file tracing doesn't pick them up from node_modules/geist).

Fix:

  • Bundle the GeistMono-Regular.ttf and GeistMono-Bold.ttf font files
    directly in the app at apps/web/app/api/og/_fonts/
  • Create a shared loadFonts() utility using fetch(new URL(..., import.meta.url))
    which ensures Next.js correctly bundles the fonts with the serverless functions
  • Update all 4 OG routes (post, home, repo, user) to use the shared utility
  • Also fix home route's icon.svg loading to use fetch instead of fs.readFile

The OG image routes were using readFile with process.cwd() to load
GeistMono fonts from node_modules, which fails on Vercel deployments
because the fonts aren't included in the serverless function bundle
(file tracing doesn't pick them up from node_modules/geist).

Fix:
- Bundle the GeistMono-Regular.ttf and GeistMono-Bold.ttf font files
  directly in the app at apps/web/app/api/og/_fonts/
- Create a shared loadFonts() utility using fetch(new URL(..., import.meta.url))
  which ensures Next.js correctly bundles the fonts with the serverless functions
- Update all 4 OG routes (post, home, repo, user) to use the shared utility
- Also fix home route's icon.svg loading to use fetch instead of fs.readFile
@vercel
Copy link
Contributor

vercel bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
forums Error Error Feb 7, 2026 9:52pm

- Replace fetch(new URL(..., import.meta.url)) with readFile + process.cwd()
  since Turbopack doesn't support fetch for file:// URLs during build
- Defer font loading to first call (lazy singleton) instead of module
  evaluation time to avoid errors during page data collection
- Add dynamic = 'force-dynamic' to /api/og/home to prevent prerendering
- Revert home route icon loading back to readFile from disk
import { join } from "path"
import { loadFonts } from "../_fonts/load-fonts"

export const dynamic = "force-dynamic"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OG image route has three critical issues: process.cwd() fails in monorepo contexts, export const dynamic = "force-dynamic" conflicts with cacheComponents config causing build errors, and font loading also uses process.cwd()

Fix on Vercel

Comment on lines +2 to +11
import { join } from "path"

let fontsPromise: ReturnType<typeof loadFontsFromDisk> | null = null

async function loadFontsFromDisk() {
const fontsDir = join(process.cwd(), "app/api/og/_fonts")

const [fontRegular, fontBold] = await Promise.all([
readFile(join(fontsDir, "GeistMono-Regular.ttf")),
readFile(join(fontsDir, "GeistMono-Bold.ttf")),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { join } from "path"
let fontsPromise: ReturnType<typeof loadFontsFromDisk> | null = null
async function loadFontsFromDisk() {
const fontsDir = join(process.cwd(), "app/api/og/_fonts")
const [fontRegular, fontBold] = await Promise.all([
readFile(join(fontsDir, "GeistMono-Regular.ttf")),
readFile(join(fontsDir, "GeistMono-Bold.ttf")),
import { fileURLToPath } from "url"
import { dirname, join } from "path"
let fontsPromise: ReturnType<typeof loadFontsFromDisk> | null = null
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
async function loadFontsFromDisk() {
const [fontRegular, fontBold] = await Promise.all([
readFile(join(__dirname, "GeistMono-Regular.ttf")),
readFile(join(__dirname, "GeistMono-Bold.ttf")),

process.cwd() fails in monorepo/serverless context when font loading tries to resolve relative paths from wrong directory

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant