How To Best Load Fonts for Web Applications

I have tried a couple different methods for loading fonts for web applications. I want to find the best method of accomplishing loading fonts.

Date Created:
Last Edited:

About Fonts


  • I use Anuphan, sans-serif for this site because I think it is a good, readable font.
    • The font was developed by Mint Tantisuwanna - a type designer at Cadson Demak
    • Anuphan is a Cadson Demak version of IBM Plex Thai
      • Cadson Demak is a high-profile foundry and typographic consultancy in Thailand
      • The IDM Plex typeface is a corporate typeface family.
  • Over 60% of websites use custom fonts, so the use of custom fonts is definitely the standard on the web.
  • Custom fonts give you the ability to include different styles of the font with a @font-face declaration in CSS or by loading the font using a <link> tag
    • Anuphan has 7 different weights that you can load.
  • I am using writing this note to see what the best way of loading a font is. Currently, I host the fonts on the server and request them in CSS files.
    • I use font-display: swap; to swap the default font with the loaded font when the the requested font is loaded.
    • I used to serve font-weights of 100, 200, 300, 400, 500, 600, and 700 to give the user the ability to have a highly customizable experience, but I don't think this is the best way to go about loading fonts.
    • I am trying to figure out what is the best combination of:
      1. Requesting font method
      2. @font-face CSS attribute values
      3. Amount of custom font styles to load
    • that result in the best experience.
Resources:

Notes on Best Practices:

  • Introduction
    • There are two main ways in which web fonts impact performance:
      • Delayed Text Rendering
        • The webpage will not render text until the web font has loaded. This slows down a webpage's First Content Paint
      • Layout Shifts
        • Font Swapping has the potential to cause content layout shifts
        • This is the problem that I am currently experiencing, but I did have the problem of fonts affecting First Content Paint before switching the font-swap property.
  • Font Loading
    • You can check when the font is requested and downloaded in the Network tab of dev tools
    • The font declared using @font-face is only downloaded when an element that uses the font is downloaded

This might affect how I bundle KaTeX, or how I use KaTeX - I currently use KaTeX in some dialogs and other places in content where the content is not user-generated, which could slow down loading. I might want to remove the KaTeX from everywhere that is not ugc to improve KaTeX loading.

Most sites would benefit form inlining font declarations and other critical styling in the <head> of the main document rather than including them in an external stylesheet. This allows the browser to discover the font declartion sooner as the browser doesn't need to wait for the external stylesheet to load.
    • If you use fonts loaded from other sites, you should consider using the link tag with the rel="preconnect" attribute to preload the fonts. This will take browser resources away from other resources that you need to load.
  • Font Delivery
    • Self Hosting Fonts
      • If you are using a self-hosted font, confirm that your site is using a content delivery network and HTTP/2.
      • You should also apply some font file optimizations that third party font providers typically provide - such as font subset-ting and WOFF2 compression.
        • Experts now recommend only using WOFF2 due to its widest browser support and best compression.
      • Subset Fonts
        • Font files typically include a large number of glyphs for all the various characters they support.
        • You may not need all those characters.
        • You can use the unicode-range descriptor in the @font-face declaration informs the browser which characters a font can be used for.
          • I think this is the range for English:
          • This is the range for basic Latin characters: U+0020-007F
          • The same font file is still downloaded when one of the characters in the unicode range is included.
        • A subset font includes a smaller portion of the glyphs that were contained in the original font file.
          • See the section below on removing unwanted glyohs from fonts.
      • Use Fewer Web Fonts
        • A system font is the default font used by the user interface of the user's device. System fonts typically vary by operating system and version. Because the font is already installed, the font does not need to be downloaded.
        • To use the system font in CSS, list the system-ui as the font-family
  • Font Rendering
    • The font-display property informs the browser how it should proceed with text rendering when the associated web font has not loaded. It is defined per font-face.
      • Possible values for font-display:

Value

Block Period

Swap Period

Auto

Varies

Varies by Browser

Block

2-3 Seconds

Infinite

Swap

0ms

Infinite

Fallback

100ms

3 seconds

Original

100ms

None

Block Period
The block period begins when the browser requests a web font. During the block period, if the web font is not available, the font is rendered in an invisible fallback font and thus the text will not be visible to the user. If the font is not available at the end of the block period, it will be rendered in the fallback font.
Swap Period
The swap period comes after the block period. If the web font becomes available during the swap period, it will be "swapped" in.
    • For this websites using a custom font where display text quickly is a top priority and where you still want to ensure the web font is used, use font-display: swap, but make sure to deliver the font early enough that it does not cause a layout shift. The downside of this option is the jarring shift when the font arrives late.
    • To reduce the CLS impact, you can use the new size-adjust attributes.
Remove Unwanted Glyphs from Fonts

Problem:

  • Font files often include many glyphs that you will not need to use in your application
  • Failing to remove these glyphs can result in larger font files which will slow down file download speed, which in turn will make the Content Layout Shift (if you are using font-display: swap; very noticeable.
  • To prevent a noticeable / large content layout shift caused by long download times for font files, you can try to speed up the file download speed by removing glyphs that are not needed from the font files.

How to Remove Unwanted Glyphs:

  1. Download fonttools: pip install fonttools
    1. The documentation for fonttols can be found here
  2. Run the subset command to only include the characters that you need
    1. What are the characters that we need?
GPT 4o Response:

Question:

give me the unicode-list css property for a @font-face declaration for a font that should only contain the characters that are commonly used on english websites

To create a custom font with a @font-face declaration that only includes characters commonly used on English websites, you can use the unicode-range CSS property. The following unicode-range values cover the basic Latin characters, punctuation, and a few additional characters that are frequently used in English.

Here's a @font-face example for such a font:
@font-face {
    font-family: 'MyCustomFont';
    src: url('MyCustomFont.woff2') format('woff2'),
         url('MyCustomFont.woff') format('woff');
    unicode-range: U+0020-007F, /* Basic Latin (includes ASCII) */
                   U+00A0-00FF, /* Latin-1 Supplement */
                   U+2010-201F, /* Punctuation */
                   U+2020-2027, /* Additional Punctuation */
                   U+2030-203E, /* Additional Punctuation */
                   U+2044, /* Fraction Slash */
                   U+20AC, /* Euro Sign */
                   U+2122, /* Trade Mark Sign */
                   U+2190-2199, /* Arrows */
                   U+2200-22FF, /* Mathematical Operators */
                   U+25A0-25FF, /* Geometric Shapes */
                   U+2600-26FF; /* Miscellaneous Symbols */
}
This unicode-range property includes:
  1. Basic Latin (U+0020-007F): This range covers standard English letters, digits, and basic punctuation.
  2. Latin-1 Supplement (U+00A0-00FF): This range includes additional punctuation and letters with diacritical marks.
  3. Specific punctuation ranges and symbols: These ranges include various punctuation marks, mathematical operators, geometric shapes, arrows, and common symbols that might be used in English text.
Feel free to adjust the unicode-range values based on the specific characters you need for your font. This setup ensures that the font file only contains the necessary glyphs, potentially reducing the file size.
  1. The command that should be run is: pyftsubset PATH_TO_FONT_FILE [glyph...] [-option=value]...
    1. At least one glyph or one of -gids, -gids-file, -glyohs, -glyphs-file, -text, -text-file, -unicodes, or -unicodes-file, must be specified
    2. Using the chat-gpt response above, the command would be: pyftsubset PATH_TO_FONT_FILE --unicodes="U+0020-007F,U+00A0-00FF,U+2010-201F,U+2020-2027,U+2030-203E,U+2044,U+20AC,U+2122,U+2190-2199,U+2200-22FF,U+25A0-25FF,U+2600-26FF" --flavor="woff2" --ignore-missing-glyphs --verbose
    3. I put these commands in a python file to make it easier to convert multiple files:
import os
# Filenames to subset font for
filenames = ["Anuphan-Bold.ttf","Anuphan-ExtraLight.ttf","Anuphan-Light.ttf","Anuphan-Medium.ttf","Anuphan-Regular.ttf","Anuphan-SemiBold.ttf"]
# Unicode glyphs to keep in output font
unicodes = "U+0020-007F,U+00A0-00FF,U+2010-201F,U+2020-2027,U+2030-203E,U+2044,U+20AC,U+2122,U+2190-2199,U+2200-22FF,U+25A0-25FF,U+2600-26FF"
for filename in filenames:
    path_to_input_file =  os.path.join(os.path.curdir,"input-fonts",filename)
    path_to_output_file =  os.path.join(os.path.curdir,"output-fonts",filename.replace(".ttf",".woff2"))
    cmd = "pyftsubset \"" + path_to_input_file + "\" --unicodes=\"U+0020-007F,U+00A0-00FF,U+2010-201F,U+2020-2027,U+2030-203E,U+2044,U+20AC,U+2122,U+2190-2199,U+2200-22FF,U+25A0-25FF,U+2600-26FF\" --output-file=\"" + path_to_output_file +"\"  --flavor=\"woff2\" --ignore-missing-glyphs --verbose"
    print(cmd)
    os.system(cmd)
  1. The results of subset-ting the Anuphan font in terms of file size and download times from the web server can be seen below
    1. The red border denotes that the specified font is not being used in production.
    2. I chose to only include some font weights to decrease size of application.
    3. The method of measuring download speeds is not scientific, but I just wanted to get an idea if there was a great affect

Filename:

Size Before Subsetting:

Size After Subsetting

Download Before Subsetting

Download After Subsetting

Anuphan-Bold.woff2

36kB

14kB

They all ended up being about the same.

Anuphan-ExtraLight.woff2

38kB

15kB

Anuphan-Light.woff2

38kB

15kB

Anuphan-Medium.woff2

42kB

15kB

Anuphan-Regular.woff2

38kB

14kB

Anuphan-Thin.woff2

34kB

N/A

Anuphan-SemiBold.woff2

42kB

15kB


Optimizing Web Font Loading

Lazy loading of fonts carries an important hidden implication that may delay text rendering. The rbowser must construct the render tree, which is dependent on the DOM and CSSOM trees. As a result, font requests are delayed well after other critical resources, and the browser may be blocked from rendering text until the resource is fetched.

By preloading WebFonts and using font-display to control how browsers behave with unavailable fonts, you can prevent blank pages and layout shifts due to font loading.

Preloading WebFont Resources

  • If you know you will need the resource, using <link rel="preload"> will trigger a request for the web font early in the critcial rendering path, with or without having to wait for the CSSOM to be created.
Customize Text Rendering Delay

  • You can customize how browsers behave when rendering text that uses a font-family that is not yet available using the font-display property inside a @font-face declaration. This was mentioned above.
The Font Loading API

  • If you need additional customizations for loading fonts, you can use the Font Loading API
Proper Caching is a Must

CSS size-adjust for @font-face

As a web font loads, you can now adjust the document font sizes and prevent layout shift when switching between fonts.
  • See this example of size adjust in action to see how using size-adjust allows for switching between font families in a seamless manner.
    • Or, use this codepen to see why the size-adjust property is needed when switching between fonts
      • Changing the typeface results in height differences in the results
      • Visually jarring content shifts
      • Moving interactive target areas
  • This can help with problems with cumulative layout shift
  • The example below uses size-adjust to scale up each glyph to 150% their original size.
@font-face {
  font-family: "Adjusted Typeface";
  size-adjust: 150%;
  src: url(some/path/to/typeface.woff2) format('woff2');
}
Calibrating fonts

  • Strategies for size-adjust calibration:
    • Remote target
      • Adjust the local fonts toward downloaded fonts
      • Use the @font-face declaration to adjust a local font in anticipation of font swapping for a custom font
@font-face {
  font-family: "Adjusted Regular Arial For Brand";
  src: local(Arial);
src: local(sans-serif);
  size-adjust: 90%;
}

@font-face {
  font-family: "Rad Brand";
  src: url(some/path/to/a.woff2) format('woff2');}

html {
  font-family: "Rad Brand", "Adjusted Regular Arial For Brand", sans-serif;
}
      • The example above adjusts the local Arial font in anticipation of a loaded custom font, for a seamless swap.
      • This strategy has an advantage of offering designers and developers the same font for sizing and typography.
    • Local target
      • Adjust downloaded fonts towards local target fonts
      • This strategy has the advantage of rendering without any adjustments, then adjusting any incoming fonts to match.
  • You can have finer tuning - other than just generic scaling - when loading fonts by using the ascent-override, descent-override, and line-gap-override properties.
    • These aren't available on safari though
  • The Compatibility table for size-adjust shows that it is available in all major browsers, so it is probably better to just use size-adjust for now rather than customizing the three properties listed above.
Conclusion

  • For fonts that are used as the main font of an application, or fonts that are used a lot through the application, it is best to load the fonts in a way that they are not blocked by CSSOM. This can be accomplished by loading the fonts inline before any CSS is declared or by preloading the fonts using <link rel="preload">
  • For fonts that are used sparingly, you should pay to attention to where you use the fonts in the application. If the font is declared in CSS, then whenever the font is declared, the font will be loaded.
  • You should optimize font files by removing glyphs that you don't need from the fonts and by compressing the fonts into the .woff file format.
  • Use the size-adjust property to prevent Cumulative layout shift.
  • For more advanced font-loading techniques, look into the font loading API in JavaScript/CSS
What I Plan on Creating

  • I plan on creating a project page where you can compare fonts in order to see what size-adjust property you need to use. (in case I don't come back to this note the link should look like: https://frankmbrown.net/projects/configure-size-adjust-property
  • The size-adjust calculator by Malte Ubl does not allow you to upload your own fonts, and it does include Anuphan. I need to find out what size-adjust value I need to use to minimize the Cumulative Layout Shift for Anuphan.
  • The page should allow users to upload font files, check the font files on the server, upload the font files to a s3 bucket (should have a cloudfront edge), load the font on the client, and allow the user to check different combinations of the size adjust property to see which one works.
Unanswered Questions

  • Would keeping the font file on a server in an edge location through a service like Amazon s3 be quicker than keeping the font file on the backend server?


Comments

You must be logged in to post a comment!

Insert Math Markup

ESC
About Inserting Math Content
Display Style:

Embed News Content

ESC
About Embedding News Content

Embed Youtube Video

ESC
Embedding Youtube Videos

Embed TikTok Video

ESC
Embedding TikTok Videos

Embed X Post

ESC
Embedding X Posts

Embed Instagram Post

ESC
Embedding Instagram Posts

Insert Details Element

ESC

Example Output:

Summary Title
You will be able to insert content here after confirming the title of the <details> element.

Insert Table

ESC
Customization
Align:
Preview:

Insert Horizontal Rule

#000000

Preview:


Insert Chart

ESC

View Content At Different Sizes

ESC

Edit Style of Block Nodes

ESC

Edit the background color, default text color, margin, padding, and border of block nodes. Editable block nodes include paragraphs, headers, and lists.

#ffffff
#000000

Edit Selected Cells

Change the background color, vertical align, and borders of the cells in the current selection.

#ffffff
Vertical Align:
Border
#000000
Border Style:

Edit Table

ESC
Customization:
Align:

Upload Lexical State

ESC

Upload a .lexical file. If the file type matches the type of the current editor, then a preview will be shown below the file input.

Upload 3D Object

ESC

Upload Jupyter Notebook

ESC

Upload a Jupyter notebook and embed the resulting HTML in the text editor.

Insert Custom HTML

ESC

Edit Image Background Color

ESC
#ffffff

Insert Columns Layout

ESC
Column Type:

Select Code Language

ESC
Select Coding Language