Fix fonts.ts crashing every client bundle that reaches it (v0.2.9)
Validate e-invoices / mustang (push) Successful in 25s

The v0.2.8 fileURLToPath fix (for a Turbopack asset-hash bug in the
Payload backend) wrongly assumed fonts.ts never reaches a browser
bundle. It does: einfach-produktiv's LiveCompanySettingsPreviewClient
renders <InvoiceDocument> client-side, and several Client Components
pull this module in transitively via the index.ts barrel just by
importing computeTaxBreakdown. fileURLToPath isn't a real function in
a browser's node:url shim, so calling it unconditionally at module
scope crashed module evaluation for any client bundle that included
it — including einfach-produktiv's entire /cart page.

Fix: branch on typeof window === "undefined" — browser keeps the
original new URL(..., import.meta.url) idiom (correctly asset-hashed
by Turbopack for a consumer's own build), Node.js keeps the
fileURLToPath/path.join resolution from v0.2.8.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-30 18:22:53 +00:00
parent ed96d36b54
commit 87cf1db330
3 changed files with 49 additions and 18 deletions
+2
View File
@@ -37,6 +37,8 @@ One canonical implementation, consumed by both repos, makes this class of drift
**2026-07-30, font path broke every PDF render inside the Payload backend (v0.2.8).** `fonts.ts` (added in v0.2.6 above) resolved the vendored `.ttf` files via `new URL('./assets/fonts/...', import.meta.url).pathname` — that exact expression shape is what Next.js's Turbopack/webpack bundler statically detects and rewrites into its own hashed static-asset pipeline (`.next/server/assets/<hash>.ttf`). Harmless in the frontend's own Next.js build, but the Payload backend is *also* a Next.js app (Payload 3's standard architecture) consuming this package as a `node_modules` git dependency — there, the hash Turbopack's compiled server code referenced didn't match what it actually emitted to disk, so every single invoice/correction-invoice download in the admin failed with `ENOENT: .../LiberationSans-Regular.<hash>.ttf`. Confirmed not a stale-build-cache issue: reproduced identically even after a from-scratch `docker compose build --no-cache`. Fixed by resolving the fonts directory via `fileURLToPath(import.meta.url)` + `path.dirname`/`path.join` instead — Turbopack's special-casing only triggers on the literal `new URL(x, import.meta.url)` pattern, not an equivalent built from `node:url`/`node:path` primitives, so this resolves to the same correct absolute path at runtime without ever entering the bundler's asset-hashing path. Since `Font.register()`'s `src` field only accepts a `string` (path/URL), not a `Buffer`, there was no way to sidestep this by embedding the font bytes directly instead.
**2026-07-30, same day: that fix broke `fonts.ts` in the browser instead (v0.2.9).** The v0.2.8 fix above assumed "this file only runs server-side (a PDF renderer has no reason to ever reach a client bundle)" — wrong on both counts: einfach-produktiv's `LiveCompanySettingsPreviewClient.tsx` renders `<InvoiceDocument>` directly in the browser for its live preview, and several of its Client Components (`CartContent.tsx`, `CheckoutContent.tsx`, etc.) pull in `fonts.ts` transitively just by importing `computeTaxBreakdown` from this package's barrel `index.ts` (which also re-exports `invoicePdf.tsx`). Calling `fileURLToPath(import.meta.url)` unconditionally at module scope — fine in Node.js, but `node:url`'s `fileURLToPath` isn't a real function in a browser bundle's polyfilled shim (Next.js still resolves the *import* to some stub object rather than erroring; only *calling* the function throws) — crashed module evaluation for every client bundle that reached this module, breaking e.g. einfach-produktiv's entire `/cart` page (`Uncaught TypeError: fileURLToPath is not a function`). Fixed by branching on `typeof window === "undefined"`: the browser path keeps the original `new URL('./file', import.meta.url)` idiom (correctly asset-hashed by Turbopack for that consumer's own build — the v0.2.8 bug only ever applied to being consumed as a `node_modules` dependency by a *different* Next.js app), the Node.js path keeps the `fileURLToPath`/`path.join` resolution from v0.2.8. Both `fonts.ts` imports stay static at the top of the file either way — only the function *calls* are gated, since the import itself never threw.
## How this is consumed
Not published to npm — installed as a git dependency: