fix(font): not emitting 400 as default value when font weight is omitted - #2796
james-elicx merged 4 commits into
Conversation
…ted (cloudflare#2793) Emitting `font-weight: 400;` when weight is omitted will clamp the wght axis of variable fonts
|
Hi! I ran into this issue while migrating my project from nextjs to vinext. The font issue can be quite noticeable after the migration, so I'd really appreciate it if you could take a look and review this PR when you have a chance. Thanks! |
|
/bigbonk review for issues and parity |
commit: |
There was a problem hiding this comment.
Review: font-weight descriptor omission
The core fix is correct and matches Next.js exactly. I verified against the Next.js canary nextFontLocalFontLoader (packages/font/src/local/loader.ts), which builds the @font-face descriptors with:
...((weight ?? defaultWeight) ? [['font-weight', weight ?? defaultWeight]] : []),The PR's const configuredWeight = src.weight ?? options.weight; followed by conditional emission mirrors this precisely. Omitting the descriptor (rather than emitting 400) is the right behavior for variable fonts with a wght axis, and it aligns with the CSS Fonts spec auto default.
Two good things worth calling out:
- The
.classNamerule path (resolveSingleFaceStyle→formatFontClassRule) already omittedfont-weightwhen unspecified (viaresolveFontWeightreturningundefined), so this fix correctly targets only the@font-facedescriptor — no double-fix or divergence introduced. - Tests are well-constructed, cite the corresponding Next.js fixtures/loader, and all pass locally (
font-local-transform,app-router-font-google-prod,pages-router-font-google-prod). Lint + fmt are clean.
Non-blocking observations
-
Repo bloat from real 2MB TTFs. The two added
noto-sans-wdth-wght.ttffiles are identical (same md5) and ~2MB each, so this adds ~4MB to the repo. vinext's local-font shim never parses font bytes (no fontkit / noadjustFontFallbackmetric extraction) — the file is only served as a static asset and no assertion inspects its contents. A tiny stub or heavily subsetted.ttfwould satisfy every test at a fraction of the size. Not blocking, but worth reconsidering before merge since these binaries live in git history forever. -
Orphaned stub fixture. See inline comment.
Nice, focused fix. Thanks for the thorough Next.js references in the test comments.
| src: "./local.woff2", | ||
| src: [ | ||
| { | ||
| path: "./noto-sans-wdth-wght.ttf", |
There was a problem hiding this comment.
This fixture no longer references ./local.woff2 after switching to noto-sans-wdth-wght.ttf. The 26-byte stub at tests/fixtures/font-google-multiple/app/local.woff2 is now orphaned and can be deleted.
(Note: tests/fixtures/font-google-pages/pages/local.woff2 must stay — it is still referenced by tests/pages-document-assets.test.ts:116.)
|
Review complete and posted to PR #2796. SummaryVerdict: The fix is correct and faithfully matches Next.js behavior. No blocking issues. What I verified
Non-blocking feedback posted
|
Closes #2793
Overview
The
next/font/localshim emitsfont-weight: 400when no weight was specified. For variable fonts with awghtaxis, this descriptor clamps the font face to its regular 400 instance, preventing heavier weights from rendering correctly even when elements usefont-weight: 600or700.This aligns vinext with Next.js and the CSS Fonts specification by allowing the descriptor to retain its default
autobehavior when omitted, while preserving explicit single weights, weight ranges, and source-level overrides. The fix applies consistently to both development and production output.What changed
The original behavior sets font weight to
400ifweightis not providedvinext/packages/vinext/src/shims/font-local.ts
Line 120 in d65ff09
vinext/packages/vinext/src/shims/font-local.ts
Lines 132 to 138 in d65ff09
The new behavior doesn't set font weight if
weightis not provided, leaving it asautorules.push(`@font-face { font-family: '${escapeCSSString(family)}'; src: url('${escapeCSSString(src.path)}') format('${format}'); - font-weight: ${weight}; - font-style: ${style}; + ${weight === undefined ? "" : ` font-weight: ${weight};\n`} font-style: ${style}; font-display: ${display}; }`);Testing
Corresponding tests are added or updated.
pnpm test tests/app-router-font-google-prod.test.ts tests/font-local-transform.test.ts tests/pages-router-font-google-prod.test.tsFixtures
Related fixtures (
font-google-pagesandfont-google-multiple) are updated. Noto sans variable font is used in the fixtures because Noto sans makes the visual difference before and after this fix much more noticeable in the fixtures.Before:

After:

Notes
I ran into this issue when migrating my project to vinext. Applying the fix is quite important to me. Thanks for reviewing!