← Back to The Mixing Bowl
building in publicengineeringproduct

The Docs-Only Commit That Still Failed to Build

By Chad Holdorf·October 12, 2026·5 min read

Bakery Buddy's `layout.tsx` used to import six font families straight from Google through Next.js's `next/font/google`, across roughly twenty weight and style combinations. That felt free. It's one import line per font, no files to manage, no licensing to track. What it actually meant was that every single production build made about twenty live network requests to fonts.gstatic.com before it would finish, and a build is not supposed to depend on the outside world cooperating at all.

A commit that cannot have a bug, and still failed

On August 13, four of that day's twenty production builds failed. Two different font requests were behind it. One of the four failing commits touched only a markdown file. No code, no logic, nothing a compiler could possibly object to. It failed anyway, because the build doesn't know or care what you changed. It rebuilds the whole app from scratch every time, and this app's "from scratch" included asking Google for fonts it had asked for a thousand times before and would ask for a thousand times again.

That's the part that's easy to miss if you're only looking at your own diff. A failed build and an unwritten feature look identical from the outside: the commit is sitting on main, the code is correct, and production is quietly still running yesterday's build. Nothing in the repository says which one happened.

The build cache didn't help either

Two days later, on August 15, it happened again, this time to one font, Cormorant Garamond, on a build with its own deployment id. The build log even said "Restored build cache from previous deployment" right before it went out and asked Google for the font anyway. The cache is for speeding up a build that still has to run, not for skipping the parts of it that reach outside Vercel's own network. Whatever you'd call that behavior, "safety net" isn't it.

One of the four August 13 failures was never manually retried at all. It only ever shipped because a later, unrelated commit's build happened to succeed and carried it along. Nobody decided that. It was luck, and it looked exactly like a deliberate fix from the outside.

What "transient" was hiding

Before any of this got named for what it was, the standing advice for a failed deploy was to treat a `next/font/google` failure as a transient, the kind of error you just retry. That's a reasonable instinct for a flaky network call. It's the wrong instinct for a build-time dependency on a specific third party's uptime, because a transient implies the next attempt is independent of the last one, and a 404 on a specific font file at a specific CDN path isn't going anywhere just because you asked Vercel to try again a minute later.

Calling it transient also meant nobody looked for the actual fix for a while, because "retry and move on" is a satisfying enough answer to stop asking why the build needed Google at all.

Owning the bytes

The fix, on August 17, was to stop asking. All six font families are now self-hosted woff2 files loaded with `next/font/local` instead, sitting in the repo as the exact same latin-subset bytes the Google-hosted version was already serving, not a redesign, just moving where the bytes live. Five of the six collapsed into a single variable font file each, since a variable file covering a wide weight range replaces what used to be several discrete requests for individual weights.

It also surfaced something the Google setup had been hiding in plain sight: the root layout was preloading eight separate font files on every single route, four of which belong only to the public bakery-website themes and can never render on a signed-in page. A signed-in baker opening `/home` was downloading about 194 KB of font files she had no way to ever see. Self-hosting made that visible enough to fix in the same pass, down to four.

Verified by opening the page, not by the build going green

None of this got checked off by `npm run build` succeeding. A clean build only proves the new local font files resolved as files, which they would either way. The actual check was opening the real page and reading `document.fonts` in the browser to confirm each font variable had actually loaded, then flipping on each bakery-site theme to confirm the three low-priority, no-longer-preloaded fonts still loaded correctly when a page actually asked for them. A screenshot of the page next to the pre-fix version was the last check, because the whole point of self-hosting the exact same subset of bytes was that nothing was supposed to look different at all.

What generalizes past fonts

Any dependency your build reaches out to at build time, a font CDN, a package registry, an API that generates a file, is now part of your deploy's uptime, whether you meant it to be or not. It doesn't matter how reliable that dependency usually is. "Usually" is exactly the gap a failed deploy lives in, and the failure doesn't announce itself as a dependency problem. It just looks like your last commit broke the build, even when that commit was a markdown file with nothing to break.

If a build-time fetch can fail, the honest fix usually isn't a smarter retry. It's asking whether the thing you're fetching needs to be fetched at deploy time at all, or whether it's small and stable enough to just commit and own.

Frequently Asked Questions

What actually broke?

layout.tsx imported six Google font families through next/font/google across about twenty weight and style combinations, so every production build made roughly twenty live network requests to Google's font servers before it could finish. Any single one of those requests returning anything but success failed the whole build. Four of twenty builds failed this way on August 13, and a fifth failed the same way two days later on a single font.

How can a docs-only commit fail to build?

A production build rebuilds the entire app from its current state every time, not just the lines that changed. One of the August 13 failures touched only a markdown file, nothing a compiler could object to, and it still failed, because the build-time fetch to Google's font servers runs regardless of what the commit actually changed.

Didn't the build cache protect against this?

No. The August 15 failure's own log showed the build cache being restored from the previous deployment, and it still went out and requested the font from Google afterward, and failed when that request didn't succeed. The cache speeds up the parts of a build that don't need the outside world; it was never going to skip a live network fetch.

What's the actual fix, and what's the general lesson?

All six font families are now self-hosted as local woff2 files loaded with next/font/local, the identical bytes the Google-hosted version already served, so no build-time network request is needed at all. The general lesson is that any dependency a build reaches out to over the network, not just a font, becomes part of that build's uptime whether you intended it to or not, and the fix for a build-time fetch that can fail is usually to stop fetching it at build time, not to retry harder.

Written by
Chad Holdorf
Founder, Bakery Buddy

I build Bakery Buddy for my wife Lindsay's cake studio, Marin Cake Studio, and this is the outage that taught me a build isn't done compiling just because your code is right.


Ready to put this into practice?

Join the Waitlist