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

audit: Web Font requests take a long time and push out FCP #3107

Closed
addyosmani opened this issue Aug 23, 2017 · 12 comments · Fixed by #3831
Closed

audit: Web Font requests take a long time and push out FCP #3107

addyosmani opened this issue Aug 23, 2017 · 12 comments · Fixed by #3831

Comments

@addyosmani
Copy link
Member

Use font-display: optional (or swap) - if you can’t load a font fast, just use a fallback until the next page load.

One thing to be careful with: font-display doesn't apply for CDN fonts like those loaded from Google Fonts or TypeKit. Should we only apply this recommendation to Web Fonts loaded from the origin?

@wardpeet
Copy link
Collaborator

wardpeet commented Sep 26, 2017

@addyosmani just to make sure it's clear. I add CSS.fontsUpdated listener to capture webfonts. If the webfonts loaded after FCP the audit fails.

We recommend font-display when audit fails and fonts load from the same origin?

So if I host my css on my cdn (akamai) and fonts are loaded from there as well than font-display will have no effect? (TIL)

@paulirish
Copy link
Member

Okay so the typical bad webfont case that we want to flag is:

  • Page is using webfonts
  • For whatever reason they take a long time to finish downloading and then rendering the updated page
  • The text is probably invisible in the meantime.

So to find these cases, we want to consider fonts who are important to the rendering of the page. And if they were downloading before FMP then they probably were "blocking" for FMP. So we'll want to flag all of these.

Their "cost" to the load would probably be from the font network request "queuing" time until its finished. Queuing will be close to "start time" so we could use that if its painful.


Font CDNs

Font CDN's are tricky because font-display works inside the @font-face rule, which is CSS that they own, not you. But if you have control over the @font-face declaration then this does apply to you.

I think we'll still want to flag GoogleFonts/Typekit but just recommend a different course of action.


recommendations

@iamstarkov
Copy link

how one would measure if font-display: swap; improve the rendering?

@wardpeet
Copy link
Collaborator

I tried a few things but it's pretty hard to figure this one out.
CSS.fontsUpdated does let me know when fonts are loaded but not which font so it's pretty useless.

I can find when a font request is finished and if it's < First Interactive it's probably blocking? I've set a simple test page with fonts loading from google cdn.

<html>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
  <body>
    <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
    <style>
      span {
        font-family: "Roboto";
      }
    </style>
    <span>Font font font</span>
    <p>no special font</p>
  </body>
  </html>

@patrickhulce
Copy link
Collaborator

I think all we're actually looking for here are font requests that finished before FCP whose initiator was the parser or a blocking stylesheet?

@wardpeet
Copy link
Collaborator

Mmh looked like fcp was lower than the webfont timestamp as other content was painted already. I'll do another check to be sure. Only taking fonts from blocking stylesheets sounds awesome. Thanks for the pointers

@paulirish
Copy link
Member

I think there's a little more to it.

There is often a gap between the end of the font download and when it's painted. (Though maybe this isn't too huge.) This time would be included in the benefit of using swap/optional.

One idea is using fontfaceobserver (or whatever that web platform API is called) to find out when each font is "loaded"

But in the meantime we could just look at network time

@addyosmani
Copy link
Member Author

Would FontFaceSet (part of the Font Loading API) help here? https://www.chromestatus.com/features/4594172182921216. It's been in Chrome for a while.

@wardpeet
Copy link
Collaborator

I found a few issues with this lighthouse when trying to figure this one out.
pass or afterpass in the gatherer phase run javascript too late. driver.evaluateAsync gets fired at 6 seconds (pageload) when fonts are already loaded. There is not a way for me to interact when we navigate to the page.

When creating a demo page

<html>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
  <body>
    <script>
      document.fonts.ready.then(() => {
        window.__FONTSREADY = performance.now();
      });
    </script>
    <style>
      @font-face {
        font-family: 'Roboto';
        font-style: normal;
        font-weight: 400;
        src: url(https://fonts.gstatic.com/s/roboto/v18/oMMgfZMQthOryQo9n22dcuvvDin1pK8aKteLpeZ5c0A.woff2) format('woff2');
        unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
      }
      span {
        font-family: "Roboto";
      }
    </style>
    <span>Font font font</span>
    <p>no special font</p>
  </body>
  </html>

I get these values:
FCP: 734.264ms
FONTSREADY: 1645.63
EvaluateAsync: 6945.225
Network font: 1581.155000001192

So fontsready is the document.fonts.ready promise that comes with the html. So it means that this is the correct value. If I look at the network records it's pretty close to download from network. Evaluateasync on the other hand is the document.fonts.ready lighthouse injects which comes too late and just resolves immediately.

Code that's injected by lighthouse

function getFonts() {
  return document.fonts.ready.then(() => {
    return performance.now()
  });
}

Devtoolslog: https://drive.google.com/open?id=0B3dMZOz0yvVQZDlNVDRtT0RWR00
trace: https://drive.google.com/open?id=0B3dMZOz0yvVQZ0Y5RkQ2Rzk4azA

@patrickhulce
Copy link
Collaborator

patrickhulce commented Oct 30, 2017

To schedule some JS at the beginning of the page load you can use driver.evaluateScriptOnNewDocument. We do this for the performance observer that stashes the property somewhere on window for future collection.

/**
* Add a script to run at load time of all future page loads.
* @param {string} scriptSource
* @return {!Promise<string>} Identifier of the added script.
*/
evaluteScriptOnNewDocument(scriptSource) {
return this.sendCommand('Page.addScriptToEvaluateOnLoad', {
scriptSource,
});
}

If there aren't cases where its a significant cost though (and it's pretty much always fixed at ~100ms) I'd be in favor of keeping it simple and just reporting network savings. I think others disagree on that point though. I think we need firmer definition of what we're highlighting here. That demo page wouldn't trigger any warning as far as I can tell since FCP was not actually pushed out at all. If we're warning about all fonts that were required, not just blocking ones, then it seems like we should move it into best practices for just missing font-display.

@wardpeet
Copy link
Collaborator

@patrickhulce correct, the demo page would not as it doesn't have a stylesheet that introduces the font face. Sometimes it can take a while to load. Also FCP is calculated pretty fast as some content is painted but not all so I don't know how relevant FCP is for fonts

<html>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
  <body>
    <script>
      document.fonts.ready.then(() => {
        window.__FONTSREADY = performance.now();
      });
    </script>
    <style>
      @font-face {
        font-family: 'Roboto';
        font-style: normal;
        font-weight: 400;
        src: url(https://fonts.gstatic.com/s/roboto/v18/oMMgfZMQthOryQo9n22dcuvvDin1pK8aKteLpeZ5c0A.woff2) format('woff2');
        unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
      }
      span {
        font-family: "Roboto";
      }
    </style>
    <span>Font font font</span>
  </body>
  </html>

will make my FCP = font loading (1.5s) which means fonts block FCP. What I'm trying to say that not all websites suffer from font blocking but of course it's not a best practice as their are many ways to do delayed loading

@paulirish
Copy link
Member

@wardpeet let's chat on video for like 30min before you get too deep here. Hopefully we can do that to avoid too much back-n-forth later. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants