Skip to main content

WOFF2 vs TTF: which file belongs on your site

I measured 13 fonts: WOFF2 cut them by 42.6–70.8%, but clearly beat plain Brotli only on TrueType outlines. What to keep, what to serve, how to verify.

Founder, CometWeb

I build CometWeb Insight, a website audit app that ends with a ranked to-do list. I write about what I measure on real sites: performance, SEO, accessibility and CO₂e.

Published: Updated:
12 min read

In one font conversion test, WOFF2 appeared to cut the file size by 94.5%. A closer look showed that Athelas.ttc contained four variants of the typeface, while the WOFF2 file contained only one. I was comparing a 2.3 MB collection with a 127 KB file holding one variant. That result could not be used to judge compression.

I excluded that case and analysed the remaining thirteen fonts. WOFF2 still produced smaller files, but the differences were less dramatic. The rest of the article explains how to compare font formats and why file size alone cannot tell you whether a page will render text sooner.

Comparison: Athelas.ttc at 2.3 MB is a collection of four faces, while the 127 KB WOFF2 held one face. Hence the apparent 94.5% reduction.
That 94.5% compared four faces with one, so the file left the measurement.macOS font measurement, 12 Sep 2026

WOFF2 vs TTF: how the two font files differ

WOFF2 is a web delivery format for fonts. According to the W3C WOFF 2.0 specification, it takes the same data as a TTF, transforms certain tables and compresses the result with Brotli. If you only need a short definition, the glossary entry for WOFF2 has one.

A browser loads either one through @font-face, but WOFF2 packs the same data tighter, and caniuse shows every major browser supports it.

Table of TTF and WOFF2. Both work in @font-face and neither makes letters sharper, but only WOFF2 has Brotli compression built into the format, so the TTF stays the source file and the WOFF2 is the file you ship to users.
You keep the TTF as the source and ship the WOFF2 to your users.W3C WOFF 2.0 and caniuse, September 2026

A .ttf file can be a valid OpenType font with TrueType or CFF/CFF2 outlines, and WOFF2 packages either.

How I measured WOFF2, TTF and plain Brotli

On 12 September 2026, I converted thirteen macOS system fonts using fontTools 4.65.0. I kept the character sets, hints and variable-font axes unchanged so the test focused on the format.

I also compressed every original with plain Brotli 1.2.0 at quality 11, to separate the compression gain from the format gain. I checked rendering on Georgia in Chrome 152 on macOS.

Each reported reduction uses the stated baseline, either the original font file or the plain Brotli result. The calculation is shown below.

reduction (%) = (baseline bytes - output bytes) / baseline bytes x 100

I measured file bytes, so you won't find production LCP, CLS or energy savings here. The methodology in the research pack has the results, script, input hashes and tool versions, if you want to re-run it.

WOFF2 cuts font files by 42.6–70.8% against the original

In September 2026, converting thirteen macOS fonts without subsetting, WOFF2 cut them by 42.6–70.8% against the original (median 54.7%). The fonts ranged from 4.5 KB to 23 MB, and file size didn't predict the saving.

Noto Sans Lycian (4,556 B) lost 51.7% against the original, almost exactly what Arial Unicode (23 MB) lost at 51.2%.

Outline type decides WOFF2's margin over plain Brotli

Compared with plain Brotli, WOFF2 won by a median 11.7% on TrueType outlines and by 0.5% on CFF outlines, which surprised me.

Median WOFF2 margin over plain Brotli: +11.7% for 9 fonts with TrueType outlines (from +0.8% to +15.0%) and +0.5% for 4 fonts with CFF outlines (from -0.7% to +0.9%).
The format's whole gain over plain Brotli sits in TrueType outlines.File size results, 12 Sep 2026

For CFF outlines it's a coin flip, because in two of four fonts plain Brotli produced the smaller file, by 0.0% and 0.7% compared with WOFF2.

Why the difference? Under the W3C spec, WOFF2 transforms TrueType glyph data such as the glyf table and leaves CFF data as it is. CFF is already compact, so WOFF2 ends up roughly where Brotli alone does.

So if your CFF font's WOFF2 barely beats the Brotli-compressed original, the tool works fine. WOFF2 stays the default anyway, because it arrives compressed with no server configuration, and a correct Content-Encoding depends on your host.

My only variable font (SF Pro, TrueType outlines) also landed at +0.8% compared with plain Brotli, like the CFF group. With n=1, that's an observation to test on a larger sample.

Results for 13 fonts: original, WOFF2 and Brotli

Georgia lost the most (70.8% against the original), Noto Sans Canadian Aboriginal the least (42.6%).

FontOutlinesGlyphsOriginal BWOFF2 BReductionBrotli BWOFF2 vs Brotli
Arial Unicodeglyf50,37723,278,00811,357,64851.2%12,921,359+12.1%
SF Proglyf2,9378,333,9642,757,52466.9%2,780,939+0.8%
Times New Romanglyf3,380824,920361,81656.1%403,946+10.4%
Courier Newglyf3,151684,624310,38454.7%351,676+11.7%
Georgiaglyf1,134379,588110,84070.8%130,472+15.0%
Verdanaglyf913186,18878,64857.8%90,649+13.2%
Noto Sans SiddhamCFF50594,40850,09246.9%50,562+0.9%
Noto Sans JavaneseCFF40586,94438,82855.3%38,821-0.0%
Noto Sans Can. AboriginalCFF75541,46823,78842.6%23,992+0.9%
Noto Sans TifinaghCFF16124,95613,30446.7%13,215-0.7%
Noto Sans Thaanaglyf9018,7928,20056.4%9,088+9.8%
Noto Sans Phoenicianglyf346,2523,47644.4%3,769+7.8%
Noto Sans Lycianglyf344,5562,20051.7%2,560+14.1%

Font file size and HTTP transfer are two different numbers

File size is the bytes on disk, and HTTP transfer is the bytes that crossed the network. A server can compress a TTF or OTF response with Content-Encoding. WOFF2 carries its compression inside the package, so it arrives compressed even without Content-Encoding: br.

The Brotli column above is an offline comparison, without response headers, transfer timing or decode cost, so check your transferred bytes in DevTools (steps below).

Separate the WOFF2 format from subsetting and hinting

Converting to WOFF2, removing unused characters and dropping hinting are three different operations. A tool may do all three and hand you one number, and that's where most "TTF vs WOFF2" comparisons fall apart.

Our own font converter works that way too, because once you select a character subset, its worker writes an intermediate TTF with hinting: false before compressing.

Three layers of work in the CometWeb font converter with a subset: the character subset, an intermediate TTF saved without hinting, and WOFF2 compression. Your screen shows only one file size, the result of all three operations.
Each of the three operations changes the file size, and your screen shows only the end result.CometWeb font converter, September 2026

That's a reasonable default for web delivery, but the output is conversion plus subsetting plus hint removal. To see what WOFF2 alone did, convert without a subset.

Don't add up percentage cuts from successive stages, because each has a different denominator, and measure the final artefact against the original.

Subset without losing the text your site needs

Start a subset from what your content needs, then check the file size. A smaller download with missing punctuation is an unfinished optimisation. Beyond the alphabet, a price needs €, a quotation needs curly quotes, and an opening-hours range uses an en dash, and one specimen sentence catches all of it.

„Zażółć gęślą jaźń” — 129 €; dostępność: 9–17…

I ran it against our latin-ext preset's ranges, meaning code points 32–126 and 160–383, 319 in total. The preset has every precomposed Polish letter, so "zł" in a price comes through fine, but it's missing these characters:

CharacterCode pointIn latin-ext?
€U+20ACNo
– en dashU+2013No
em dashU+2014No
„ ”U+201E, U+201DNo
’U+2019No
…U+2026No
combining acuteU+0301No

The preset called latin-ext omits eight characters used in Polish typography, despite its name. I checked just the requested range, without exporting a file.

Names typed into forms, new CMS posts, translations and product data bring in characters your current copy lacks. Give those surfaces broader coverage and a working fallback, and keep text-specific subsets for strings that never change.

Keep the shaping features your content needs, too. One character isn't necessarily one glyph, and a subsetter may need to keep ligature and positioning glyphs.

What unicode-range does in CSS

The CSS unicode-range descriptor tells the browser which characters a face should cover. According to MDN, if your page uses even one character in the range, the whole font is downloaded. So physical subsets need separate files and declarations. A broad CSS declaration also can't restore a character removed during export.

Georgia as TTF and WOFF2: same width, 0 differing pixels

Repackaging Georgia from TTF to WOFF2 changed nothing in rendering. I measured the DOM width and the rendered canvas pixels at device pixel ratio 2:

28px  „Zażółć gęślą jaźń” — 129 €; 9–17…
      TTF 429.805 px   WOFF2 429.805 px   delta 0.000 px   differing pixels: 0

16px  Hamburgefonstiv 0123456789 fi fl ffi — AV To Wa
      TTF 328.633 px   WOFF2 328.633 px   delta 0.000 px   differing pixels: 0

I count pixels because text can take the same width and still draw differently. Two strings in one browser is a narrow check, and the raw results say as much, so run it on your own fonts too.

Static or variable font: compare the set your page uses

A variable font can replace several static files, but it isn't automatically smaller than one static face. Compare the complete set your page needs, at equivalent character coverage.

Include italics if you use them, because an upright variable font doesn't necessarily contain them. Use the axis range your file actually has, instead of copying 100 900 into every declaration.

How to serve WOFF2: @font-face, font-display and preload

Serving WOFF2 takes one @font-face declaration with one source and an explicit loading strategy:

@font-face {
  font-family: "Brand Example";
  src: url("/fonts/brand-regular-v1.woff2") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

body {
  font-family: "Brand Example", system-ui, sans-serif;
}

This declaration covers one face, upright at weight 400, and a bold file needs its own declaration. For a variable file whose weight axis really spans 100–800:

@font-face {
  font-family: "Brand Variable Example";
  src: url("/fonts/brand-variable-v1.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
}

If a legacy requirement calls for WOFF, list it after WOFF2 in the same src. Under CSS Fonts Level 4 the browser uses the first source it can load, so when the WOFF2 loads, the WOFF isn't fetched.

Choose a font loading strategy

The font-display descriptor decides what happens to your text while the font arrives. According to CSS Fonts Level 4, the initial value is auto, which leaves the choice to the browser, and the table summarises MDN's description of the values.

ValueTrade-off
swapFallback appears almost immediately, webfont swaps in later
fallbackLimited window for the swap
optionalNo later swap; the visit may keep fallback
blockLonger invisible-text period before fallback

With swap, the switch can change line wrapping when the font metrics differ. You can align your fallback face with size-adjust and the metric overrides, calculated for your own font pair.

Smaller font bytes can help text-based LCP (the time to render your page's largest element), but a percentage cut in file size doesn't predict the same cut in LCP. So test your page, and see how I read that kind of test in my comparison of lab data and CrUX field data.

Preload fonts selectively

<link rel="preload" href="/fonts/brand-regular-v1.woff2" as="font" type="font/woff2" crossorigin>

Preload brings forward a request the browser would otherwise discover late, so reserve it for the faces your first screen needs, and use the same URL as your CSS. MDN's preload guide says to keep the crossorigin attribute even for same-origin files.

Serve your font file as Content-Type: font/woff2. An HTML error page isn't a font just because the URL ends in .woff2. A content-hashed URL can take Cache-Control: public, max-age=31536000, immutable, but only if changed content always gets a new URL.

Verify the exported font in DevTools

  1. Open the Network panel, filter to fonts and reload with cache disabled. Check the URL, status, MIME type and transferred bytes.
  2. Repeat with cache enabled, and judge those loads separately from first visits.
  3. Check which font drew the text, because fallback quietly covers gaps. Test your body text, headings, form inputs and prices in every language you support.
  4. Change one thing at a time. Keep your format, subset, loading strategy and preload changes separate until you know what each one did. If several ship together, report the outcome as a combined change.

WOFF2 is the easy part of font optimisation

The harder part is a subset that keeps its characters, and honest measurement. That's where one number fools you, like Athelas.ttc fooled me.

I cover the other critical resources (images, JavaScript, server response) in 7 ways to speed up your website.

After you deploy WOFF2, use DevTools to check that the browser downloads the right file and how the text behaves while loading. The Performance module in CometWeb Insight can help you assess page performance after the change. Create a free Insight account, then choose your URL and scan scope.

Frequently asked questions

Is WOFF2 better than TTF for websites?

For delivery to modern browsers, WOFF2 is better than TTF. TTF remains a valid source file, so keep the original and serve a verified web asset.

Do I need both WOFF and WOFF2?

Add WOFF only if a supported environment requires it. Every major browser handles WOFF2.

Can I convert WOFF to WOFF2?

Yes, converting WOFF to WOFF2 is lossless in itself. But start from an authoritative original, because converting an existing subset can't restore characters already removed.

Will WOFF2 fix layout shifts (CLS)?

WOFF2 won't fix CLS by itself. Layout shifts depend on the loading strategy and the fallback font's metrics.

Can I just rename .ttf to .woff2?

Renaming a .ttf file to .woff2 isn't enough. The file has to be encoded as WOFF2 with a converter.

Sources and test data

  1. Methodology
  2. File-size results
  3. Browser check
  4. Script
  5. W3C: WOFF File Format 2.0
  6. W3C: CSS Fonts Module Level 4, 0
  7. W3C: CSS Fonts Module Level 4, 0
  8. MDN: 0
  9. MDN: 0
  10. MDN: 0
  11. Can I use: WOFF 2.0

The package doesn't include the fonts, which are system fonts licensed by their suppliers. Check your rights to a font before you put it on the web.

The research-pack links were checked on 24 September 2026.

Check your site in Insight

Free gives you one project and 60 credits a month. That covers a Standard audit with a priority list.

No credit card Report with date and sources Free account in the app