Link to the code that reproduces this issue
https://github.com/lucasmcht-corp/next-font-404-repro
To Reproduce
git clone https://github.com/lucasmcht-corp/next-font-404-repro
cd next-font-404-repro
npm install
npm run build:mocked
The build exits 1 with:
Received response with status 404 when requesting https://fonts.gstatic.com/s/bitter/v42/this-file-does-not-exist.woff2
> Build error occurred
Error: Module not found: Can't resolve '@vercel/turbopack-next/internal/font/google/font'
The stylesheet is served through Next's own NEXT_FONT_GOOGLE_MOCKED_RESPONSES hook, so no CDN behaviour is involved. It points at a font file URL that does not exist, so the file fetch, which is not mocked, answers 404 on every machine, every time.
Current vs. Expected behavior
Current: a single failed font file download stops the build with Module not found: Can't resolve '@vercel/turbopack-next/internal/font/google/font'. The HTTP status appears only as a warning earlier in the log, easily lost in a large build. There is no retry.
Expected: the fetch is retried, and if it still fails the build error names the network failure, the URL and the status code, instead of an internal module.
Where it happens
In crates/next-core/src/next_font/google/mod.rs:
Why this matters outside the mock
The same path is reached with no mock at all. On 16.3.1 our production builds failed at random, on a different family each run (Bitter, Montserrat, Noto Serif), with .next deleted before each build, because the stylesheet returned by fonts.googleapis.com pointed at files fonts.gstatic.com had already rotated away. Checked again while writing this:
| URL |
status |
.../notoserif/v33/ga63aw1J5X9T9RW6...QLqNQw.woff2 (requested by the build) |
404 |
.../notoserif/v33/ga6daw1J5X9T9RW6...WsNFHuQk.woff2 (returned today for the same family) |
200 |
A likely trigger, offered as a hypothesis since I could not capture the stylesheet the failing build received: fonts.googleapis.com/css2 answers with cache-control: private, max-age=86400, stale-while-revalidate=604800 and vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site. The fetcher sends no Sec-Fetch-* headers, so it addresses its own cache entry, one browsers never keep warm, which may be served stale for up to seven days while the files it names are rotated.
That hypothesis is only about what makes the fetch fail. The reproduction above is about what Next does when it does fail, which is deterministic and independent of it.
Suggested fix, in order of value
- Retry the font file fetch before giving up.
- Report the HTTP status and URL as the build error instead of a missing module.
- Optionally send
Sec-Fetch-Dest: style on the stylesheet request, so the fetcher shares the cache entry browsers keep warm.
Related
Environment
next 16.3.1
node 22.23.0
Linux x64
App Router, next/font/google
Link to the code that reproduces this issue
https://github.com/lucasmcht-corp/next-font-404-repro
To Reproduce
git clone https://github.com/lucasmcht-corp/next-font-404-repro cd next-font-404-repro npm install npm run build:mockedThe build exits 1 with:
The stylesheet is served through Next's own
NEXT_FONT_GOOGLE_MOCKED_RESPONSEShook, so no CDN behaviour is involved. It points at a font file URL that does not exist, so the file fetch, which is not mocked, answers 404 on every machine, every time.Current vs. Expected behavior
Current: a single failed font file download stops the build with
Module not found: Can't resolve '@vercel/turbopack-next/internal/font/google/font'. The HTTP status appears only as a warning earlier in the log, easily lost in a large build. There is no retry.Expected: the fetch is retried, and if it still fails the build error names the network failure, the URL and the status code, instead of an internal module.
Where it happens
In
crates/next-core/src/next_font/google/mod.rs:fetch_from_google_fonts, L662-681 makes one request, emits an issue atIssueSeverity::Warning, returnsNone.NextFontGoogleFontFileReplacer, L419-425 turns thatNoneintoResolveResult::unresolvable().Why this matters outside the mock
The same path is reached with no mock at all. On 16.3.1 our production builds failed at random, on a different family each run (
Bitter,Montserrat,Noto Serif), with.nextdeleted before each build, because the stylesheet returned byfonts.googleapis.compointed at filesfonts.gstatic.comhad already rotated away. Checked again while writing this:.../notoserif/v33/ga63aw1J5X9T9RW6...QLqNQw.woff2(requested by the build).../notoserif/v33/ga6daw1J5X9T9RW6...WsNFHuQk.woff2(returned today for the same family)A likely trigger, offered as a hypothesis since I could not capture the stylesheet the failing build received:
fonts.googleapis.com/css2answers withcache-control: private, max-age=86400, stale-while-revalidate=604800andvary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site. The fetcher sends noSec-Fetch-*headers, so it addresses its own cache entry, one browsers never keep warm, which may be served stale for up to seven days while the files it names are rotated.That hypothesis is only about what makes the fetch fail. The reproduction above is about what Next does when it does fail, which is deterministic and independent of it.
Suggested fix, in order of value
Sec-Fetch-Dest: styleon the stylesheet request, so the fetcher shares the cache entry browsers keep warm.Related
Environment