High Performance
Web Fonts

Stairway - Izabela Bołoz

"Væsnet bag Muren" - Torbjørn Vinter & Christian Rask 

"Olsen Banden på lur" - Preowned

"Sortedammens mytografi" - Frederik Hesseldahl & Tore Rørbæk

"Metropolitain Jungle" - Peter Skensved & Alexandre de Girardier

"Evolution part 2" - Colaboration coordinated by Ulrik Schiødt

👋  Hi

~70%

use web fonts

 

First Paint

Is it happening?

First Meaningful Paint

Is it useful?

User Experience

Hello World!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello World</title>

  
  
  
  <link rel="stylesheet" href="style.css">

</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello World</title>

  <link rel="stylesheet"
        href="https://fonts.googleapis.com/css?family=Tangerine:700">

  <link rel="stylesheet" href="style.css">

</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

Performance impact: Google Fonts

What is slowing us down?

Moto 4G, slow 3G connection (global average)

0.0s

4.1s

6.5s

Is it happening?

First paint

First meaningful paint

Invisible Text

 

Render Blocking

Loading Fonts Faster

Loading Fonts Faster

Font Loading Techniques

I didn't know webfont loading had enough content for a talk

... or that there were so many techniques to choose from

Font Loading Techniques

Font Loading Trade-offs

Font Loading Strategy

Automation

Guidelines

  • Tool adapts to your code
  • Works with all build pipelines
  • No JS dependency
  • No server component

Enghave Plads. Photo: Ditte Valente / Metroselskabet

Controlling rendering: font-display

Controlling rendering: font-display

Controlling rendering: font-display

@font-face {
  font-family: 'Tangerine';
  font-style: normal;
  font-weight: 700;
  src: url('fonts/tangerine-700.woff2') format('woff2'),
       url('fonts/tangerine-700.woff') format('woff');
  
  font-display: swap;
}

Controlling rendering: font-display

Trades

Rendering control

Frederiksberg. Photo: Reginaldo Sales / Metroselskabet

Controlling latency: Self-hosting

Controlling latency: Self-hosting

Controlling latency: Self-hosting

<head>
  <style>
    @font-face {
      font-family: 'Tangerine';
      font-style: normal;
      font-weight: 400;
      src: local('Tangerine Regular'), local('Tangerine-Regular'),
           url('../fonts/tangerine-v11-latin-regular.woff2') format('woff2'),
           url('../fonts/tangerine-v11-latin-regular.woff') format('woff')
    }
  </style>

  <link rel="stylesheet" href="style.css">

</head>

Controlling latency: Self-hosting

Controlling latency: Self-hosting

Trades

No external CSS

No extra connections

Faster loading

Cache-control

No CDN

No subsetting

No auto-updates

Gammel Strand. Photo: Reginaldo Sales / Metroselskabet

Controlling delay: Preload

Controlling delay: Preload

<!-- 83.34% global support: https://caniuse.com/#feat=link-rel-preload -->
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
      href="fonts/tangerine-v9-latin-700.woff2">

<style>
  @font-face {
    font-family: 'Tangerine';
    font-style: normal;
    font-weight: 700;
    src: url('fonts/tangerine-v9-latin-700.woff2') format('woff2'),
          url('fonts/tangerine-v9-latin-700.woff') format('woff');
  }
</style>

<script>
  /* 91.61% global support: https://caniuse.com/#feat=font-loading */
  try {
    document.fonts.forEach(function(f) {
      f.family.indexOf('Tangerine') !== -1 && f.load()
    })
  } catch (e) {}
</script>

Controlling delay: Preload

Controlling delay: Preload

Trades

Faster loading

Delay other assets

Østerport station. Photo: Reginaldo Sales / Metroselskabet

Controlling size: Subsetting

Controlling size: Subsetting

Image Source: Bram Stein

Controlling size: Subsetting

/* greek */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFVp0bbck.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}

/* latin */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFVZ0b.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
                 U+FEFF, U+FFFD;
}

Guideline Intervention!

No Server Component

Controlling size: Subsetting

@font-face {
  font-family: 'Tangerine';
  src: url('fonts/tangerine-full.woff2') format('woff2');
}






h1 {
  font-family: 'Tangerine', 'Brush Script MT', sans-serif;
}

Controlling size: Subsetting

@font-face {
  font-family: 'Tangerine';
  src: url('fonts/tangerine-full.woff2') format('woff2');
}

@font-face {
  font-family: 'Tangerine__subset';
  src: url('fonts/tangerine-subset.woff2') format('woff2');
}

h1 {
  font-family: 'Tangerine__subset', 'Tangerine', sans-serif;
}

Superset Fallback

Image Source: Bram Stein

What characters am I using?

Controlling size: Subsetting

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Tangerine&text=hello%20world">
brew install fonttools brotli zopfli

pyftsubset Tangerine.woff \
  --output-file=Tangering_subset.woff \
  --flavor=woff \
  --obfuscate_names \
  --with-zopfli
  --text="hello world"

Controlling size: Subsetting

Controlling size: Subsetting

Trades

Fewer bytes

External dependencies

Complex workflows

Fallback typography flaws

 

Copenhagen Airport Station

Subfont

Automated web font loading optimization

npx subfont hello-world-google-fonts.html -i --inline-css

Subfont

Automated web font loading optimization

<head>
  <link href="https://fonts.googleapis.com/css?family=Tangerine" rel="stylesheet">
</head>
<head>
    <link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
        href="/subfont/Tangerine-700-7d6d4056f7.woff2">
    <style>
        @font-face {
            font-display: swap;
            font-family: Tangerine__subset;
            font-stretch: normal;
            font-style: normal;
            font-weight: 700;
            src: url(/subfont/Tangerine-700-7d6d4056f7.woff2) format("woff2"),
                 url(/subfont/Tangerine-700-a6bf1c959e.woff) format("woff")
        }
    </style>
    <script>
      try {
        document.fonts.forEach(function(f) {
          f.family.indexOf('__subset') !== -1 && f.load();
        });
      } catch (e) {}
    </script>
    <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin="anonymous">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
</head>

<body>
    <script>
      (function() {
        var el = document.createElement('link');
        el.href = 'https://fonts.googleapis.com/css?family=Tangerine:700'.toString(
          'url'
        );
        el.rel = 'stylesheet';
        document.body.appendChild(el);
      })();  
    </script>
    <noscript>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Tangerine:700">
    </noscript>
</body>

Subfont

Automated web font loading optimization

diff --git a/package.json b/package.json
index 9d2a2b9..3b632b5 100644
--- a/package.json
+++ b/package.json
@@ -5,9 +5,13 @@
   "main": "index.js",
   "scripts": {
     "prebuild": "rm -rf dist",
-    "build": "cp -r src dist"
+    "build": "cp -r src dist",
+    "postbuild": "subfont -i --inline-css dist/index.html"
   },
   "keywords": [],
   "author": "",
-  "license": "ISC"
+  "license": "ISC",
+  "devDependencies": {
+    "subfont": "^3.7.0"
+  }
 }
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..51c50c2
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+fonttools
+brotli
+zopfli

Photo: Pelle Rink / Metroselskabet

Trade-offs: Downsides

Trade-offs: Downsides

Self-hosting: No CDN

Commoditized:
Cloudflare, Netlify, Zeit, Cdnify, etc.

Trade-offs: Downsides

Self-hosting: No subsetting

Subsetting handled by Subfont

Trade-offs: Downsides

Self-hosting: No auto-updates

Run Subfont on each deploy

Trade-offs: Downsides

Preload: Delay other assets

Reduced payloads minimizes problem
Preload your CSS with high priority

Trade-offs: Downsides

Subsetting: Complex workflows

Abstracted away by Subfont

Trade-offs: Downsides

Subsetting: External dependencies

Future: HarfbuzzJS

Trade-offs: Downsides

Subsetting: Fallback typography flaws

Subsetting: Fallback typography glitches

Subsetting: Fallback typography glitches

Subfont  💖  JAMstack

The Future 

THANKS!