fonts icon indicating copy to clipboard operation
fonts copied to clipboard

css with both woff2 (with `unicodeRange`) + woff sources loads both

Open neobutter opened this issue 1 year ago • 11 comments
trafficstars

I created a simple example from the official site of Nuxt font.

https://stackblitz.com/~/github.com/neobutter/nuxt-font-woff2

If you run this project, you will see that you are downloading WOFF fonts, not WOFF2 fonts.

As far as I analyzed, the generated css file has unicode-range set for woff2 fonts, but not for woff fonts. (This can also be checked via Nuxt dev tools).

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xFIzIXKMnyrYk-0OGiuKW0Qv.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: "Roboto Fallback: Arial";
    src: local("Arial");
    size-adjust: 99.7809%;
    ascent-override: 92.9771%;
    descent-override: 24.4677%;
    line-gap-override: 0%
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xMIzIXKMnyrYk-FtUkPdqKUJ.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xEIzIXKMnyrYk-hpp7JRBVzs.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+1F00-1FFF;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xLIzIXKMnyrYk-GW4TW82VwC.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xHIzIXKMnyrYk-o3jsGxQAtr.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xGIzIXKMnyrYk-9a09QjonqI.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xIIzIXKMny-qOuywMEC2V.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz-O9XKdmxavM.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz-FqAWiXi2ZB.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz-6HweffhGyI.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+1F00-1FFF;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz-pW7uC1Xcsx.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz-zggmXoxUPL.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz-NqIDrIk3Rk.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Mu4mxKKTU1Kg-2ZlIYAHJdJ.woff2) format(woff2);
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    font-weight: 400;
    font-style: normal
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu52xM-gaQaf7WhjM.woff) format(woff);
    font-display: swap;
    font-weight: 400;
    font-style: italic
}

@font-face {
    font-family: Roboto;
    src: local("Roboto Regular"), local("Roboto"), url(/_fonts/KFOmCnqEu92Fr1Me5g-h5LDVhWuVU.woff) format(woff);
    font-display: swap;
    font-weight: 400;
    font-style: normal
}

div[data-v-51c93bf8] {
    font-family: Roboto, "Roboto Fallback: Arial", sans-serif
}

My guess is that woff doesn't have unicode-range, so it loaded woff fonts instead of woff2.

I'm currently working on a project, and this issue prevented me from using this module. (I used google-fonts (https://google-fonts.nuxtjs.org/).

If this is a bug, I would appreciate a fix.

(I am not a native English language speaker, please forgive me if I was rude).

neobutter avatar Jul 22 '24 14:07 neobutter

Hey there, @neobutter! I'm Dosu, a bot here to help you with bugs, questions, and becoming a contributor. Let me know how I can assist you! 🤖

The issue of WOFF fonts being downloaded instead of WOFF2 fonts in your Nuxt project can be fixed by ensuring that the font sources are correctly specified as WOFF2 in your configuration. From your provided CSS, it appears that the @font-face rules are correctly specifying WOFF2 fonts with unicode-range.

However, the presence of WOFF fonts without unicode-range might be causing the browser to prioritize them. To ensure WOFF2 fonts are prioritized, you can explicitly specify the WOFF2 format first and ensure that WOFF fonts are not included unless necessary.

Here is an example of how you can structure your @font-face rules to prioritize WOFF2:

@font-face {
    font-family: 'Roboto';
    src: local("Roboto Regular Italic"), local("Roboto Italic"), url(/_fonts/KFOkCnqEu92Fr1Mu51xFIzIXKMnyrYk-0OGiuKW0Qv.woff2) format('woff2');
    font-display: swap;
    unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
    font-weight: 400;
    font-style: italic;
}

/* Repeat for other unicode ranges and styles */

Ensure that all your @font-face rules prioritize WOFF2 and avoid including WOFF formats unless absolutely necessary. This should help the browser to load WOFF2 fonts first.

Additionally, you can check your Nuxt configuration to ensure that it is set up to prioritize WOFF2 fonts. Here is an example configuration:

export default defineNuxtConfig({
  modules: ['@nuxt/fonts'],
  fonts: {
    families: [
      { name: 'Roboto', src: '/path-to-roboto.woff2' },
      // Add other fonts as needed
    ],
  },
});

By ensuring that your configuration and CSS prioritize WOFF2 formats, you can resolve the issue of WOFF fonts being downloaded instead of WOFF2 fonts [1].

To continue talking to Dosu, mention @dosu.

dosubot[bot] avatar Jul 22 '24 14:07 dosubot[bot]

I still don't get this. I want to include woff2 and woff, but somehow in my @font-face it puts the woff before the woff2. So woff to is prioritised now instead of woff2.

@font-face {
  font-family: Lufga;
  src: url(/fonts/custom-thin.woff) format(woff),
    url(/fonts/custom-thin.woff2) format(woff2);
  font-display: swap;
  font-weight: 100;
  font-style: normal;
}
@font-face {
  font-family: Lufga;
  src: url(/fonts/custom-extra-light-italic.woff) format(woff),
    url(/fonts/custom-extra-light-italic.woff2) format(woff2);
  font-display: swap;
  font-weight: 200;
  font-style: italic;
}

/* ... */

Why isn't this automatically done?

stijns96 avatar Aug 21 '24 06:08 stijns96

@stijns96 Would you provide a reproduction, please? 🙏 I take it you're using the local provider?

danielroe avatar Sep 23 '24 10:09 danielroe

let's track the wrong woff2 priority in https://github.com/nuxt/fonts/issues/135 - now resolved in https://github.com/nuxt/fonts/commit/b087d47ec514f72820109cfdb0bf833480fde7d7.

... and leave this issue to track woff2 not being loaded due to unicodeRange.

danielroe avatar Sep 23 '24 11:09 danielroe

Is there a fix for this?

The browsers ends up loading a 66 KB woff font for me, instead of an 8 KB woff2. This module does more harm than good for optimization (for those who do not check). 😢

HummingMind avatar Nov 14 '24 21:11 HummingMind

would you be happy with only the woff2 font being loaded?

danielroe avatar Nov 14 '24 21:11 danielroe

would you be happy with only the woff2 font being loaded?

I can't speak for everyone. But for my projects, woff2 is all I need. I appreciate the work on this. Didn't mean to sound direct. Just not sure if people check the size of what is being served, and bandwidth is expensive. Thank you for all you do fot Nuxt! 🍻

HummingMind avatar Nov 14 '24 21:11 HummingMind

Hi @danielroe ,

I think I rather only load woff2 and use a fallback font, then loading woff.

But that some personal preference

stijns96 avatar Nov 14 '24 22:11 stijns96

Also, Things seem to work better with fontsource as the provider, instead of google. At least I get the correct subset and format loaded.

🍻

HummingMind avatar Nov 14 '24 23:11 HummingMind

For what it's worth, I had a quick check and this seems to be browser behaviour (preferring the font without unicode range). I'm not quite sure why - I'd need to dive deeper.

When omitting the .woff font face, the browser then prefers the subsetted font.

We need to consider the best approach here but I suspect we should omit fallbacks if unicodeRange is present on at least one rendered font.

wdyt @qwerzl?

danielroe avatar May 09 '25 14:05 danielroe

Looks like a browser bug indeed. On my device, Chrome fetches the woff2 file, but Safari fetches the woff one.

I think the culprit might be that the woff2 definitions are being overridden by the woff definitions in Webkit. Safari does fetch the woff2 files if I override the response and place the woff definitions above the woff2 definitions. I'll do some further testings once I have time.

qwerzl avatar May 10 '25 02:05 qwerzl

Hi, I've also encountered this browser quirk.

I've done some testing with Roboto from Google Fonts using version 0.11.4 and, in my case, it looks like the issue is triggered by a combination of font weights and styles. I've not tried with different unicode ranges. All of these results are from nuxt generate:

weights styles result
['400'] normal woff
['400'] normal, italic woff
['400', '500', '600', '700'] normal woff
['400', '500', '600', '700'] normal, italic woff
['400 700'] normal woff2
['400 700'] normal, italic normal as woff2 / italic as woff

So it seems like the browser only prefers woff2 when the font is variable-width. I've also encountered another bug where setting preload: true preloads the cyrillic version of the font, but that's probably another issue.

If anyone is looking for a workaround, you can define a custom font provider that filters to only include woff2 fonts. This also corrects the preload issue (at least in my case, ymmv):

server/fontProvider.ts

import {
    createUnifont,
    defineFontProvider,
    providers,
    type FontFaceData,
    type LocalFontSource,
    type RemoteFontSource,
} from 'unifont';

function isWoff2Font(src: LocalFontSource | RemoteFontSource) {
    return 'format' in src && src.format === 'woff2';
}

function hasLatinRange(fontFaceData: FontFaceData) {
    return fontFaceData.unicodeRange?.some((range) => range.startsWith('U+0000'));
}

function fontPriorityComparator(a: FontFaceData, b: FontFaceData) {
    const aNormalStyle = a.style === 'normal';
    const bNormalStyle = b.style === 'normal';
    if (aNormalStyle && !bNormalStyle) {
        return -1;
    }
    if (!aNormalStyle && bNormalStyle) {
        return 1;
    }

    const aLatin = hasLatinRange(a);
    const bLatin = hasLatinRange(b);
    if (aLatin && !bLatin) {
        return -1;
    }
    if (!aLatin && bLatin) {
        return 1;
    }

    return 0;
}

export default defineFontProvider('custom', async () => {
    const unifont = await createUnifont([providers.google()]);

    return {
        async resolveFont(fontFamily, options) {
            const result = await unifont.resolveFont(fontFamily, options);

            for (const font of result.fonts) {
                font.src = font.src.filter(isWoff2Font);
            }

            result.fonts = result.fonts.filter((font) => font.src.length > 0).sort(fontPriorityComparator);

            return result;
        },
    };
});

nuxt.config.ts

fonts: {
    providers: {
        'google-woff2': './server/fontProvider',
    },
    provider: 'google-woff2',
}

@danielroe let me know if there's anything I can do to help.

giakki avatar Aug 13 '25 11:08 giakki

Oh, I should add that the test above was with the google provider; bunny does not have this problem. Since that provider does not support variable fonts, I'll keep using my workaround, but should the issue to be reported to unifont?

giakki avatar Aug 13 '25 12:08 giakki