Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Font Optimization] Asynchronous non-critical font styles #16065

Closed
joe-bell opened this issue Aug 11, 2020 · 9 comments
Closed

[Font Optimization] Asynchronous non-critical font styles #16065

joe-bell opened this issue Aug 11, 2020 · 9 comments
Milestone

Comments

@joe-bell
Copy link
Contributor

joe-bell commented Aug 11, 2020

Feature request

Whilst I hugely appreciate @prateekbh's work to enhance the external font loading experience, it would be nice to support (or maybe even make default) the option to treat these font styles as non-critical.

RE: #14746, #16031

Describe the solution you'd like

An async option could be defined in the experimental config (but I would argue it might be best to assume that font styles are non-critical and set this to true by default) which would extract the external CSS into a separate CSS file.

This CSS file would be referenced in the head, and set to media="print" on initial load then to media="all" on app hydration. With lots of modern CSS-in-JS libraries extracting to critical CSS, this would avoid clashing opinions on what should actually by considered crticial.

Describe alternatives you've considered

I built and maintain next-google-fonts but this doesn't extract CSS at compile time; it just asynchronously loads Google Fonts.

Additional context

Again, thanks so much for your work on this @prateekbh. Happy to help out where I can

@prateekbh
Copy link
Contributor

prateekbh commented Aug 11, 2020

@joe-bell thanks for the feedback, highly appreciate the interest. I did investigate the approach you mentioned. However there are some pitfalls to the same.

  • The default priority for browser to fetch font definitions and fonts is HIGHEST. It needs this info to paint the page quickly when the font-display is blocking and also helps in displaying the correct font ASAP with font-display optional. Both of these impact the core web vitals. So with the current approach the priorities remain effectively unchanged.

  • I have not tested this but the approach of async stylesheet might harm font-icons. With print=media the stylesheet is fetched at the LOWEST priority and thus the user will see the text instead of the icon for a very long time.

Let me know if there are alternate findings

@joe-bell
Copy link
Contributor Author

@prateekbh thanks for the detailed feedback here, it's helpful to understand how this plays apart in the Core Web Vitals work!

More and more people are choosing to use font-display: swap; as fonts are not considered critical against layout styles. It's even the default option in Google Fonts now. Having an option to render the fonts asynchronously–so they are not blocking other styles–would bring huge perf gains and avoid dreaded FOUT.
I'm not sure if it's worth worrying about font icons any more, now that SVG is considered to be the best approach.

I would really recommend taking a look at this recent article by @csswizardry; "The Fastest Google Fonts". It does a good job of explaining how important this feature would be

@prateekbh
Copy link
Contributor

Thanks for the links @joe-bell. I'll address the concerns in the following points

  1. I left font display: swap intentionally as the effect on CLS from display: swap and perf gains from both approaches would be effectively same. Either early(via current approach) or late(via async css) there would be some layout shift so the net effect on web vitals are same.

  2. SVG is considered to be the best approach.

I completely agree with this and don't wanna encourage otherwise. I just mentioned it for completeness sake.

  1. The Async CSS
    There are three main concerns that I have
  • Delayed display
    The Async CSS approach is intentionally delaying the loading of the fonts by using media=print. This is unintuitive for most people coming from any other web development background. If developers host CSS for fonts or download the font and refer them in their CSS they would still see the font declarations arriving along with the CSS and not after.
    While using the components like next-google-fonts or any other specialized ones, the users make an intentional choice of delaying the fonts and know that fonts are not one of the important part of the product. Where as making it a default forces this call on them. Based on product requirements a font might still be an important part of branding e.g. (I am taking a guess here) see google photos, medium, airbnb etc.

  • Extensibility
    While display: swap renders the text ASAP it may still have CLS issue when a font of a different height is loaded on the page. Our team is currently investigating if this can be mitigated in some way. By inling the fonts declarations the users have a choice to switch to display: blocking(if fonts are utmost importance) or display optional(something we're looking into to eliminate CLS on pages).
    So the way I look at this is that impact radius of improvement is slightly bigger with the current approach. These options go away when we make lazy loading of any sort as the default. With Async CSS the display: blocking would be a terrible UX and display: optional would also have FOUT.

  • Minimally Invasive
    There might be some repetition from point 1 but the current approach the idea of inlining the fonts css got us to eliminate the render blocking aspect without changing the DX or the impact on the loading waterfall. That said, we still have a step left of having a threshold to inling the font declarations in order to prevent the markup become huge. The strategy of Async CSS can definitely come in handy for loading the CSS beyond such a threshold.

Note: I agree that there might be a use case where someone would want to use async css to load fonts and I would still recommend component like next-google-fonts for the use case.

@joe-bell
Copy link
Contributor Author

joe-bell commented Aug 13, 2020

@prateekbh gotcha, thanks for taking the time to writing this up

I think I agree in this case that making this the default option is not a good idea. However, what approach would you suggest to make sure people who are using font-display: swap can avoid FOUT due to render-blocking CSS?

Do you still think it's viable to have this async feature available for those using font-display: swap (or maybe even automatically if they pass the Google Fonts parameter), or would you see this as out-of-scope for the current experimental implementation?

@prateekbh
Copy link
Contributor

Well choosing display=swap makes your text appear faster but definitely has a problem of FOUT.
I will soon look into the effects of display:optional + adding a preload with this optimization. This option adds a 100ms wait to FCP and can render the text in actual font without FOUT.

I would suggest people to opt for the async css when they are very sure that fonts are of least interest but not something to come out of the box, given that if you're adding a font in the first place you might want it to load faster as well than later.

@willemmulder
Copy link

As a small sidetrack: if I want to use

<link rel="preload" href="https://..." as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://...></noscript>

to make loading CSS async, that does not work because I'd have to rework that to using onLoad with a function, but that function is never executed. So

  1. Is there a way to just insert the above HTML as-is, without having to comply to JSX?
  2. If not, is there a way to make onLoad work?
  3. If not, is there another easy way to make external CSS load async?

@joe-bell
Copy link
Contributor Author

joe-bell commented Dec 1, 2020

Hey @willemmulder, I think this might be useful to add over at #12984

@Timer Timer added this to the 10.x.x milestone Jan 4, 2021
nicholaschiang added a commit to tutorbookapp/tutorbook that referenced this issue Mar 28, 2021
Use what is currently the best workflow for importing variable web fonts
from the Google Fonts API:
- https://css-tricks.com/how-to-load-fonts-in-a-way-that-fights-fout-and-makes-lighthouse-happy/
- https://csswizardry.com/2020/05/the-fastest-google-fonts/

Perhaps in the future I'll look into self-hosting fonts, but Google
Fonts is nice because Chrome and other browsers are able to cache the
responses and reuse them across websites.

Related to: vercel/next.js#16065
nicholaschiang added a commit to tutorbookapp/tutorbook that referenced this issue Mar 28, 2021
Use what is currently the best workflow for importing variable web fonts
from the Google Fonts API:
- https://css-tricks.com/how-to-load-fonts-in-a-way-that-fights-fout-and-makes-lighthouse-happy/
- https://csswizardry.com/2020/05/the-fastest-google-fonts/

Perhaps in the future I'll look into self-hosting fonts, but Google
Fonts is nice because Chrome and other browsers are able to cache the
responses and reuse them across websites.

Related to: vercel/next.js#16065
@joe-bell
Copy link
Contributor Author

Just tried out "Automatic Webfont Optimization" in [email protected] with &display=swap, and everything worked as expected (as seen on joebell.co.uk) – so I'm happy to close this issue

Thanks for the cracking discussion ✌️

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants