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

Perf: improve Google Web Font performance #2639

Merged
merged 2 commits into from
Jan 29, 2025
Merged

Perf: improve Google Web Font performance #2639

merged 2 commits into from
Jan 29, 2025

Conversation

lalver1
Copy link
Member

@lalver1 lalver1 commented Jan 21, 2025

Closes #2492
This PR audits and improves how the Google Web Font is loaded.

Background

In base.html, the external font (Roboto) is loaded first, and the local font (Public Sans) is loaded after. Since there are no conflicts, Roboto takes precedence. Then, Roboto is set for --bs-font-sans-serif so it is used throughout the site. The local font is only used for login.gov buttons.

Audit

  • Double-check that we are using all 3 Roboto weights: 400 normal, 500 normal, 700 normal. We are using all 3 weights.
  • Use font-display: swap and preload. Lighthouse reports an improvement in performance from 92 to 97 when using these attributes on <link>.
  • Ensure there is a good fall-back default font. The first fallback font is system-ui which is a good fallback.

@lalver1 lalver1 self-assigned this Jan 21, 2025
@github-actions github-actions bot added deployment-dev [auto] Changes that will trigger a deploy if merged to dev front-end HTML/CSS/JavaScript and Django templates labels Jan 21, 2025
Copy link

github-actions bot commented Jan 21, 2025

Coverage report

This PR does not seem to contain any modification to coverable code.

@lalver1 lalver1 marked this pull request as ready for review January 21, 2025 23:30
@lalver1 lalver1 requested a review from a team as a code owner January 21, 2025 23:30
@thekaveman
Copy link
Member

I've been looking at this. Have a few questions, writing them up as a review comment.

Copy link
Member

@thekaveman thekaveman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed in my browser console (FF 134.0.1 64-bit on Linux) these warnings related to preloading:

image

I'm looking at the MDN docs for rel="preload", and specifically What types of content can be preloaded?:

  • font: Font file.
  • ...
  • ...

Note: font... preloading requires the crossorigin attribute to be set; see CORS-enabled fetches below.

Then if we look at that CORS-enabled fetches section:

When preloading resources that are fetched with CORS enabled (e.g. fetch(), XMLHttpRequest or fonts), special care needs to be taken to setting the crossorigin attribute on your element. The attribute needs to be set to match the resource's CORS and credentials mode, even when the fetch is not cross-origin.

As mentioned above, one interesting case where this applies is font files. Because of various reasons, these have to be fetched using anonymous-mode CORS (see Font fetching requirements).

I would hope Google allows CORS requests for fonts since this seems like a common use-case and clear-cut requirement, but I'm not sure?

It is worth adding the crossorigin="anonymous" attribute to the tag and seeing if it gets rid of this warning. We use this elsewhere e.g. for Bootstrap CSS

@machikoyasuda
Copy link
Member

Getting the same warnings as @thekaveman on Mac OS/Firefox and Mac OS/Chrome:

image

Safari, renders the same (fallback font) but no error/warning message in console.

@lalver1 lalver1 force-pushed the perf/web-fonts branch 2 times, most recently from c0e86a4 to 9ae7973 Compare January 23, 2025 17:59
@lalver1
Copy link
Member Author

lalver1 commented Jan 23, 2025

Thanks for the comments @machikoyasuda and @thekaveman!

I re-tested using macOS/Firefox, macOS/Chrome, and macOS/Safari and found a few things (in addition to the crossorigin="anonymous" attribute) that I had overlooked. Here's a summary of the changes:

  • Replaced type="text/css" with as="font" following the optimization docs in 2b25225. Previously, I had mistakenly kept both attributes.
  • Moved the Google font url from the CSP style-src directive to a (new) font-src directive in 9ae7973 (since the Roboto font tag changed from a text/css type to a font type) to avoid this issue reported by Chrome and Safari (not sure why Firefox didn't show an issue):
    image
  • All 3 browsers reported this same issue (The resource https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap was preloaded using link preload but not used within a few seconds from the window's load event.) but the description in Chrome was the most specific:
    image
    Maybe this means that currently we aren't benefiting too much from preload, but I would guess it's still a good optimization to have.

@angela-tran
Copy link
Member

I'm looking at the MDN docs for rel="preload", and specifically What types of content can be preloaded?:

  • font: Font file.
  • ...
  • ...

Note: font... preloading requires the crossorigin attribute to be set; see CORS-enabled fetches below.

  • Replaced type="text/css" with as="font" following the optimization docs in 2b25225. Previously, I had mistakenly kept both attributes.

https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap is a CSS file that loads font files, so I don't think using as="font" is correct.

@angela-tran
Copy link
Member

The documentation linked by #2492 is pretty general and assumes you're loading actual font files.

I found some resources more specific to loading resources from the Google Fonts API, which is what we're doing to load Roboto.

I think it could help to look at the network waterfall chart in the devtools before and after making changes to see if changes are working as intended.

@lalver1
Copy link
Member Author

lalver1 commented Jan 24, 2025

Thanks @angela-tran!
That's a good point about the as attribute. Reading this mdn doc I thought that setting as="font" was appropriate (since it mentions it applies to CSS @font-face) but I agree with you, other docs such as this web.dev doc show that as="font" is meant for preloading font files, not CSS. Also, setting as="style" has the added benefit that I don't need to make any changes to our Django-CSP configuration 🙂 and I think that's a cleaner solution.

I'm still looking at this because even though the GET Google font was successful (I can see it in the Network tab of devtools), the page isn't rendering with Roboto, it's falling back to the System font. However, if I implement this:

<link rel="preload" as="style" href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet" type="text/css">

the page renders using Roboto. I just want to make sure that having both <link> tags is correct, it's just that from the documentation I had the impression that only the first <link> tag was needed.

@thekaveman
Copy link
Member

I just want to make sure that having both <link> tags is correct

We should only need a single tag here right? This doesn't make sense to me.

@angela-tran
Copy link
Member

I just want to make sure that having both <link> tags is correct

We should only need a single tag here right? This doesn't make sense to me.

The first link tells the preload scanner about the resource but doesn't actually download it yet. From MDN docs on preload:

Even though the name contains the term load, it doesn't load and execute the script but only schedules it to be downloaded and cached with a higher priority.

The second link actually causes the download of the resource.

@thekaveman
Copy link
Member

Doh, thank you @angela-tran 🙏

@angela-tran
Copy link
Member

@lalver1 What you're seeing makes sense with the docs on preload and also with these two StackOverflow answers to the question I linked in #2639 (comment) :

@lalver1
Copy link
Member Author

lalver1 commented Jan 28, 2025

After more testing (running scenarios 10 times and calculating the median) and reading more about the difference between preconnect and preload, I decided to make some changes to how I initially implemented this performance improvement.

In summary, we now warm up the connection to the Roboto web font using preconnect and still use display=swap to display the content immediately without waiting for the web font to download:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap"
rel="stylesheet" type="text/css">

The Largest Contentful Paint (LCP) (sec) is 0.13 and we see no warnings in the browser's console.

There are several reasons (informed by article 1, and article 2) why I ended up going this route:

  • The Google Fonts HTML snippet uses preconnect (I made slight modifications to not use their v2 API update)
    image

  • I noticed that the LCP was actually worse using preload:

<link rel="preload" as="style" href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet" type="text/css">

LCP (sec): 0.17

  • Both Chrome and Firefox reported several warnings when using preload
    image
    image

  • The Google font stylesheet is loaded twice when using preload (although it seems that concurrently)
    image

My only remaining concern is that Chrome's Lighthouse analysis gives main a score of 97 and this branch a score of 91. But this contradicts what I am seeing using other metrics

branch Chrome (LCP in Performance tool, sec) Firefox (Network tool, finish time, msec)
main 0.15 762
perf/web-fonts 0.13 676.5

Copy link
Member

@angela-tran angela-tran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look good to me! Thanks for the links to those articles.

@machikoyasuda machikoyasuda self-requested a review January 29, 2025 19:26
Copy link
Member

@machikoyasuda machikoyasuda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this locally on Mac/Safari, Chrome, Firefox. 👍

@lalver1 lalver1 merged commit b0dd698 into main Jan 29, 2025
9 checks passed
@lalver1 lalver1 deleted the perf/web-fonts branch January 29, 2025 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
deployment-dev [auto] Changes that will trigger a deploy if merged to dev front-end HTML/CSS/JavaScript and Django templates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Web perf: Audit and improve Google Web Font performance
4 participants