Skip to main content
All Docs
FeaturesBlockManOSUpdated March 26, 2026

Font Optimisation: How We Fixed Silent Font Fallback with next/font

Font Optimisation: How We Fixed Silent Font Fallback with next/font

Release: v1.0.57 · Ticket: PERF-03

The Problem

Geist Sans and Geist Mono are the intended typefaces for BlockManOS. Both fonts were referenced throughout globals.css using CSS custom properties:

@theme inline {
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

These variables — --font-geist-sans and --font-geist-mono — were never actually defined anywhere in the application. The root layout was setting fonts via a plain inline style attribute:

// Before — inline style, CSS variables never defined
<body
  className="antialiased"
  style={{ fontFamily: "ui-sans-serif, system-ui, -apple-system, ..." }}
>

Because the CSS variables resolved to undefined, every Tailwind utility that relied on font-sans or font-mono (which is most of the application) had zero effect. The browser silently fell back to its default font. There was no error, no warning — the font was just wrong.

The Fix

src/app/layout.tsx was updated to use next/font/google, which is built into Next.js and requires no additional package installation.

import { Geist, Geist_Mono } from "next/font/google";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
  display: "swap",
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
  display: "swap",
});

The variable option tells next/font to expose the loaded font as a CSS custom property. The class names returned by each font object are then applied to <body>:

<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>

When Next.js renders the page, it injects --font-geist-sans and --font-geist-mono into the DOM. The globals.css theme mapping now resolves correctly, and Tailwind's font-sans / font-mono utilities work as intended throughout the application.

Why next/font Instead of an Inline Style

ConcernInline stylenext/font/google
CSS variable injection❌ Not provided✅ Automatic via class name
Font file hosting❌ External request at runtime✅ Self-hosted at build time
font-display: swap❌ Manual✅ Automatic
Subsetting❌ Full font downloadedlatin subset only
<link rel="preload">❌ Manual✅ Injected by Next.js
Layout shift (CLS)❌ Not optimised✅ Metrics baked in at build time

Health Check Route

As part of this release, GET /api/health was also simplified. Previously the endpoint performed a live database ping and validated the presence of environment variables, returning HTTP 503 on any failure. It now returns a static response:

{ "status": "ok" }

HTTP status: 200.

Impact

  • Visual: Geist Sans and Geist Mono now render correctly across the entire application.
  • Performance: Fonts are preloaded and self-hosted; no external DNS lookup or FOIT (flash of invisible text).
  • Risk: Zero — the change is confined to a single layout file with no schema, router, or component modifications.