From 962da0124e1a7e345b15d038be9da58925147cd2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 05:25:51 +0000 Subject: [PATCH] =?UTF-8?q?ops(frontend):=20self-host=20fonts=20via=20@fon?= =?UTF-8?q?tsource=20=E2=80=94=20eliminates=20build-time=20Google=20Fonts?= =?UTF-8?q?=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #96 CI run #249 (docs(phase4.5c) merge to main) failed the Frontend (build) job with: NextFontError: Failed to fetch \`IBM Plex Sans\` from Google Fonts. at app/layout.tsx — IBM_Plex_Sans({subsets: ['latin'], weight: ['400','500','600','700'], display: 'swap', variable: '--font-ibm-plex-sans'}) A transient Google Fonts network blip during CI build broke `next/font/google`. The very next CI run (PR #97 4.5d) went green again — so the failure was a flaky external service, not a code regression. Production was unaffected (compute-rankings.yml runs independently of ci.yml). This commit closes the door on the failure mode for good by moving all 3 fonts from `next/font/google` to `@fontsource/*` npm packages (SIL Open Font License, OFL — free to redistribute): - `@fontsource/ibm-plex-sans` @ ^5.2.8 - `@fontsource/jetbrains-mono` @ ^5.2.8 - `@fontsource/instrument-serif` @ ^5.2.8 @fontsource bundles the woff2 files into node_modules, so the Next.js build never touches an external host. ## Changes | File | Change | |---|---| | `frontend/package.json` | + 3 `@fontsource/*` dependencies (and corresponding `package-lock.json` entries) | | `frontend/app/layout.tsx` | Drop `next/font/google` imports (`IBM_Plex_Sans`, `JetBrains_Mono`, `Instrument_Serif`). Drop the `className={...variable}` stack on ``. CSS variables now declared in globals.css. | | `frontend/app/globals.css` | + 9 `@import '@fontsource/*'` lines (Plex 400/500/600/700, JetBrains 400/500/600, Instrument 400 normal + italic — exactly matches the prior next/font config). + 3 new CSS variable declarations (`--font-ibm-plex-sans`, `--font-jetbrains-mono`, `--font-instrument-serif`) so existing `--font-sans/mono/serif` chains keep resolving the same fallback stack. | ## Backward compat - All existing consumers (Tailwind utility classes, `StockLogo.tsx`'s inline `fontFamily: 'var(--font-mono)'`, globals.css base-style font-family rules) continue to read the same CSS variable names — no rename ripple-through. - Font-display behavior: @fontsource defaults to `font-display: swap` which matches the previous `display: 'swap'` config. - Bundle size impact: +~140KB woff2 files (vs the previous Google Fonts CDN fetch). Negligible on the static export; the resilience win outweighs the size cost. ## Verification - ✅ `next build` — 506 static pages built; no NextFontError - ✅ `tsc --noEmit` — clean - ✅ No external font fetch in build logs (verified via build output) - ✅ Build offline-capable from now on ## Why @fontsource not vendored woff2 files - @fontsource packages are the OFL-compliant official source - Updates ship via `npm update` if the IBM Plex / JetBrains / Instrument projects release new revisions - No license/attribution risk vs hand-vendoring binary files ## Not in this PR - npm audit warnings (5 vulns, 1 critical) are in the existing Next.js dep tree per issue #41 (Next.js 14 → 16 bump). Not introduced by this PR; tracked separately. https://claude.ai/code/session_015649aRyi2bvciQYZVNACd2 --- frontend/app/globals.css | 32 ++++++++++++++++++++++++++++++-- frontend/app/layout.tsx | 32 +++++++++----------------------- frontend/package-lock.json | 30 ++++++++++++++++++++++++++++++ frontend/package.json | 3 +++ 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/frontend/app/globals.css b/frontend/app/globals.css index d448c612..fd9456aa 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -2,10 +2,38 @@ @tailwind components; @tailwind utilities; -/* Fonts wired via next/font in app/layout.tsx; CSS variables exposed - * here so any utility class or inline style can reference them. */ +/* PR 4.5d follow-up — self-hosted fonts via @fontsource packages. + * Replaces the previous `next/font/google` path that broke on a + * transient Google Fonts fetch failure during CI build (PR #96 + * CI run #249, 2026-05-17). @fontsource ships woff2 files bundled + * in node_modules; no build-time external fetch. + * + * IBM Plex Sans, JetBrains Mono — weights 400 / 500 / 600 / 700 + * (Plex) and 400 / 500 / 600 (JetBrains). Instrument Serif — + * weight 400 normal + italic. Matches the prior `next/font/google` + * config exactly. Each `@import` pulls one woff2 + its @font-face + * declaration. + * + * Font family CSS variables sit alongside the legacy + * `--font-{ibm-plex-sans, jetbrains-mono, instrument-serif}` + * names so existing utility classes + inline-style consumers + * (StockLogo.tsx: `fontFamily: 'var(--font-mono)'`) keep working + * without rename. */ +@import '@fontsource/ibm-plex-sans/400.css'; +@import '@fontsource/ibm-plex-sans/500.css'; +@import '@fontsource/ibm-plex-sans/600.css'; +@import '@fontsource/ibm-plex-sans/700.css'; +@import '@fontsource/jetbrains-mono/400.css'; +@import '@fontsource/jetbrains-mono/500.css'; +@import '@fontsource/jetbrains-mono/600.css'; +@import '@fontsource/instrument-serif/400.css'; +@import '@fontsource/instrument-serif/400-italic.css'; + :root { color-scheme: light; + --font-ibm-plex-sans: 'IBM Plex Sans'; + --font-jetbrains-mono: 'JetBrains Mono'; + --font-instrument-serif: 'Instrument Serif'; --font-sans: var(--font-ibm-plex-sans), ui-sans-serif, system-ui, -apple-system, sans-serif; --font-mono: var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, Menlo, monospace; --font-serif: var(--font-instrument-serif), ui-serif, Georgia, serif; diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index a24a0dfc..c50b5836 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,29 +1,15 @@ import type { Metadata } from 'next'; -import { IBM_Plex_Sans, Instrument_Serif, JetBrains_Mono } from 'next/font/google'; import './globals.css'; import { Disclaimer } from '@/components/Disclaimer'; -const ibmPlexSans = IBM_Plex_Sans({ - subsets: ['latin'], - weight: ['400', '500', '600', '700'], - display: 'swap', - variable: '--font-ibm-plex-sans', -}); - -const jetbrainsMono = JetBrains_Mono({ - subsets: ['latin'], - weight: ['400', '500', '600'], - display: 'swap', - variable: '--font-jetbrains-mono', -}); - -const instrumentSerif = Instrument_Serif({ - subsets: ['latin'], - weight: ['400'], - style: ['normal', 'italic'], - display: 'swap', - variable: '--font-instrument-serif', -}); +// PR 4.5d follow-up — self-host all 3 fonts via @fontsource packages +// (SIL Open Font License). Previous `next/font/google` path failed +// CI on 2026-05-17 (PR #96 CI run #249, NextFontError: Failed to +// fetch IBM Plex Sans from Google Fonts) on a transient Google +// Fonts network blip. @fontsource bundles the woff2 files into +// node_modules so the build never touches an external host. CSS +// variables `--font-{ibm-plex-sans, jetbrains-mono, instrument-serif}` +// are now declared in globals.css instead of injected by `next/font`. export const metadata: Metadata = { title: 'QuantRank', @@ -33,7 +19,7 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8b875597..4d974172 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "quantrank-frontend", "version": "0.1.0", "dependencies": { + "@fontsource/ibm-plex-sans": "^5.2.8", + "@fontsource/instrument-serif": "^5.2.8", + "@fontsource/jetbrains-mono": "^5.2.8", "next": "14.2.15", "react": "18.3.1", "react-dom": "18.3.1", @@ -144,6 +147,33 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fontsource/ibm-plex-sans": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans/-/ibm-plex-sans-5.2.8.tgz", + "integrity": "sha512-eztSXjDhPhcpxNIiGTgMebdLP9qS4rWkysuE1V7c+DjOR0qiezaiDaTwQE7bTnG5HxAY/8M43XKDvs3cYq6ZYQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/instrument-serif": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/instrument-serif/-/instrument-serif-5.2.8.tgz", + "integrity": "sha512-s+bkz+syj2rO00Rmq9g0P+PwuLig33DR1xDR8pTWmovH1pUjwnncrFk++q9mmOex8fUQ7oW80gPpPDaw7V1MMw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/jetbrains-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", + "integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", diff --git a/frontend/package.json b/frontend/package.json index 90ba1c2c..d64f8800 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,9 @@ "lint": "next lint" }, "dependencies": { + "@fontsource/ibm-plex-sans": "^5.2.8", + "@fontsource/instrument-serif": "^5.2.8", + "@fontsource/jetbrains-mono": "^5.2.8", "next": "14.2.15", "react": "18.3.1", "react-dom": "18.3.1",