[PR #893] [MERGED] fix(next/font/google): use real axis range, validate options at build time (#885) #925

Closed
opened 2026-05-06 13:10:52 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/893
Author: @NathanDrake2406
Created: 4/25/2026
Status: Merged
Merged: 4/27/2026
Merged by: @james-elicx

Base: mainHead: nathan/fix-font-google-narrow-axis


📝 Commits (4)

  • 86dfebd shim: rewrite buildGoogleFontsUrl on top of the new build-url helper
  • 8d20bbe plugin: validate options against metadata, surface HTTP errors as build errors
  • 5f68e02 tests: regression coverage for the #885 narrow-axis bug and validator throws
  • 50e0b79 address google font review feedback

📊 Changes

6 files changed (+354 additions, -81 deletions)

View changed files

📝 examples/app-router-nitro/app/layout.tsx (+1 -1)
📝 packages/vinext/src/build/google-fonts/validate.ts (+6 -0)
📝 packages/vinext/src/plugins/fonts.ts (+71 -37)
📝 packages/vinext/src/shims/font-google-base.ts (+38 -34)
📝 tests/font-google.test.ts (+228 -9)
📝 tests/google-fonts/validate.test.ts (+10 -0)

📄 Description

What this changes

Wires the metadata-driven Google Fonts pipeline from #892 into the two consumers that previously hardcoded :wght@100..900. Fixes issue #885 and related Google Fonts URL bugs.

File Change
shims/font-google-base.ts Rewrite buildGoogleFontsUrl on top of the pure URL builder. No metadata in the shim, so the Worker bundle stays small. Empty options and weight: 'variable' no longer emit invalid fallback axes.
plugins/fonts.ts Replace inline URL construction with validateGoogleFontOptions + getFontAxes + buildGoogleFontsUrl. Surface validation errors and HTTP 4xx/5xx as build errors, with bounded Google response bodies.
build/google-fonts/validate.ts Report non-array axes values before font capability checks so object-form migration errors are clearer.
examples/app-router-nitro Add the required subsets option to its Geist_Mono call so the example still builds under the stricter validator.
tests/* Add regression coverage for narrow axes, italic-only URLs, weight: 'variable' fallback behavior, plugin-level axes, validation errors, HTTP error truncation, and network-error fallback.

Why

Issue #885: vinext hardcoded :wght@100..900 in two URL builders, so fonts whose wght axis is narrower (Sen 400..800, Anton 400) returned HTTP 400 from Google and rendered as sans-serif fallback. Investigation found related cases broken by the same code path: italic-only style requests dropped italic in dev fallback, the documented axes option was ignored by the plugin path, weight: 'variable' could produce a rejected dev fallback URL, and unknown families silently produced URLs that Google rejected at request time.

Approach

  1. shim: wrap the pure URL builder. Empty options now emit no axis segment; italic-only requests now keep the ital axis; unresolved weight: 'variable' is dropped in the dev fallback because the shim intentionally has no metadata.
  2. plugin: validate at transform time, fetch with the metadata-driven URL, distinguish HTTP errors from network errors. HTTP 4xx/5xx surfaces as a build error with the URL and a bounded response body; network errors still fall through silently for offline dev.
  3. tests/examples: lock in the reported regression and review feedback, including the Nitro example build, exact italic axis values, valid axes: ['opsz'] through the plugin pipeline, and clearer object-form axes errors.

Validation

  • vp test run tests/font-google.test.ts tests/google-fonts/build-url.test.ts tests/google-fonts/validate.test.ts: 120 tests pass
  • vp check examples/app-router-nitro/app/layout.tsx packages/vinext/src/plugins/fonts.ts packages/vinext/src/shims/font-google-base.ts packages/vinext/src/build/google-fonts/validate.ts tests/font-google.test.ts tests/google-fonts/validate.test.ts: clean
  • vp run --filter vinext-app-router-nitro build: pass

Risks / release notes

  • fontCache is keyed by URL. Existing on-disk caches under .vinext/fonts/<hash>/ are stale for affected fonts; one refetch per font on next build.
  • Validator is now strict about subsets when preload is enabled, matching Next.js. User code that relied on vinext's pre-fix permissiveness needs subsets: [...] or preload: false; in-tree examples now satisfy this.
  • Italic-only dev fallback requests now emit only ital=1 instead of accidentally also shipping the regular face. This matches Next.js and the intent of style: ['italic'], but it is user-visible for code that relied on the old side effect.
  • Shim's dev fallback emits no axis segment for empty options or unresolved weight: 'variable', so dev mode under dynamic options returns Google's default static face instead of a full variable range. Production uses the metadata-aware path and is unaffected.
  • The "auto-disables preload" test in #892 pins on Playwrite AR Guides (empty subsets in current upstream metadata). If a future canary refresh adds a subset to that family the fixture needs to change.

Closes #885.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/893 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/25/2026 **Status:** ✅ Merged **Merged:** 4/27/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `nathan/fix-font-google-narrow-axis` --- ### 📝 Commits (4) - [`86dfebd`](https://github.com/cloudflare/vinext/commit/86dfebd011dd891cb5eec138ee4cf3f52ac3d12a) shim: rewrite buildGoogleFontsUrl on top of the new build-url helper - [`8d20bbe`](https://github.com/cloudflare/vinext/commit/8d20bbeb813f0dbf3e127f20045c2aa92af35b35) plugin: validate options against metadata, surface HTTP errors as build errors - [`5f68e02`](https://github.com/cloudflare/vinext/commit/5f68e0248f62a69811aa0f2c8ebd18ebec6a3bdb) tests: regression coverage for the #885 narrow-axis bug and validator throws - [`50e0b79`](https://github.com/cloudflare/vinext/commit/50e0b795235766e5335f83cbaf1e7216cc2cb57d) address google font review feedback ### 📊 Changes **6 files changed** (+354 additions, -81 deletions) <details> <summary>View changed files</summary> 📝 `examples/app-router-nitro/app/layout.tsx` (+1 -1) 📝 `packages/vinext/src/build/google-fonts/validate.ts` (+6 -0) 📝 `packages/vinext/src/plugins/fonts.ts` (+71 -37) 📝 `packages/vinext/src/shims/font-google-base.ts` (+38 -34) 📝 `tests/font-google.test.ts` (+228 -9) 📝 `tests/google-fonts/validate.test.ts` (+10 -0) </details> ### 📄 Description ## What this changes Wires the metadata-driven Google Fonts pipeline from #892 into the two consumers that previously hardcoded `:wght@100..900`. Fixes issue #885 and related Google Fonts URL bugs. | File | Change | |---|---| | `shims/font-google-base.ts` | Rewrite `buildGoogleFontsUrl` on top of the pure URL builder. No metadata in the shim, so the Worker bundle stays small. Empty options and `weight: 'variable'` no longer emit invalid fallback axes. | | `plugins/fonts.ts` | Replace inline URL construction with `validateGoogleFontOptions` + `getFontAxes` + `buildGoogleFontsUrl`. Surface validation errors and HTTP 4xx/5xx as build errors, with bounded Google response bodies. | | `build/google-fonts/validate.ts` | Report non-array `axes` values before font capability checks so object-form migration errors are clearer. | | `examples/app-router-nitro` | Add the required `subsets` option to its `Geist_Mono` call so the example still builds under the stricter validator. | | `tests/*` | Add regression coverage for narrow axes, italic-only URLs, `weight: 'variable'` fallback behavior, plugin-level `axes`, validation errors, HTTP error truncation, and network-error fallback. | ## Why Issue #885: vinext hardcoded `:wght@100..900` in two URL builders, so fonts whose `wght` axis is narrower (Sen 400..800, Anton 400) returned HTTP 400 from Google and rendered as `sans-serif` fallback. Investigation found related cases broken by the same code path: italic-only style requests dropped italic in dev fallback, the documented `axes` option was ignored by the plugin path, `weight: 'variable'` could produce a rejected dev fallback URL, and unknown families silently produced URLs that Google rejected at request time. ## Approach 1. **shim**: wrap the pure URL builder. Empty options now emit no axis segment; italic-only requests now keep the ital axis; unresolved `weight: 'variable'` is dropped in the dev fallback because the shim intentionally has no metadata. 2. **plugin**: validate at transform time, fetch with the metadata-driven URL, distinguish HTTP errors from network errors. HTTP 4xx/5xx surfaces as a build error with the URL and a bounded response body; network errors still fall through silently for offline dev. 3. **tests/examples**: lock in the reported regression and review feedback, including the Nitro example build, exact italic axis values, valid `axes: ['opsz']` through the plugin pipeline, and clearer object-form axes errors. ## Validation - `vp test run tests/font-google.test.ts tests/google-fonts/build-url.test.ts tests/google-fonts/validate.test.ts`: 120 tests pass - `vp check examples/app-router-nitro/app/layout.tsx packages/vinext/src/plugins/fonts.ts packages/vinext/src/shims/font-google-base.ts packages/vinext/src/build/google-fonts/validate.ts tests/font-google.test.ts tests/google-fonts/validate.test.ts`: clean - `vp run --filter vinext-app-router-nitro build`: pass ## Risks / release notes - `fontCache` is keyed by URL. Existing on-disk caches under `.vinext/fonts/<hash>/` are stale for affected fonts; one refetch per font on next build. - Validator is now strict about subsets when preload is enabled, matching Next.js. User code that relied on vinext's pre-fix permissiveness needs `subsets: [...]` or `preload: false`; in-tree examples now satisfy this. - Italic-only dev fallback requests now emit only `ital=1` instead of accidentally also shipping the regular face. This matches Next.js and the intent of `style: ['italic']`, but it is user-visible for code that relied on the old side effect. - Shim's dev fallback emits no axis segment for empty options or unresolved `weight: 'variable'`, so dev mode under dynamic options returns Google's default static face instead of a full variable range. Production uses the metadata-aware path and is unaffected. - The "auto-disables preload" test in #892 pins on `Playwrite AR Guides` (empty subsets in current upstream metadata). If a future canary refresh adds a subset to that family the fixture needs to change. Closes #885. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:10:52 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#925
No description provided.