Technote

Performance optimization Practical

Series Korean web typography Part 5 of 8

Subsetting Korean: not downloading all 11,172 syllables

There is no mystery to why Korean fonts are heavy: modern Hangul alone has 11,172 syllable blocks. Split with unicode-range, the browser fetches only the pieces a page actually uses.

A Latin font file is typically a few tens of kilobytes. A Korean font of the same quality is in a different league, and the reason is arithmetic rather than mystery.

Why Korean fonts are heavy

Modern Hangul composes 19 initial consonants × 21 vowels × 28 final positions (including “none”) = 11,172 syllable blocks. The Unicode Hangul Syllables block, U+AC00–U+D7A3, is exactly that range. Add the jamo themselves, Latin letters, digits and punctuation, and — for fonts that carry them — several thousand Han characters on top.

Where a Latin font holds a few hundred glyphs, a Korean font holds more than ten thousand. Yet any single page uses a small fraction of them; the syllables that appear in everyday Korean writing are heavily concentrated in a small part of the range.

Conceptual — the exact ratio varies by document, but the direction never does

What unicode-range does

unicode-range on an @font-face declares “this file covers this range of characters”. The browser looks at the characters actually present on the page and requests only the files responsible for them. The other declarations are parsed and never fetched.

The key is that many @font-face rules share one font-family name. In CSS it looks like a single typeface; on the network it is many small pieces.

/* Latin, digits, punctuation */
@font-face {
  font-family: 'Pretendard';
  src: url('/fonts/pretendard-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2000-206F;
}

/* One Hangul chunk — in practice dozens of these follow */
@font-face {
  font-family: 'Pretendard';
  src: url('/fonts/pretendard-korean-12.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+AC00-AF33;
}

⚠ One declaration without a range defeats the whole split. An @font-face that omits unicode-range claims every character, so that file is downloaded in full. When splitting appears not to work, look here first.

Static subset or dynamic split

There are broadly two approaches.

A static subset contains only the glyphs your site actually uses, in one file. It is the lightest option and it breaks when the content changes: publish a new article or add a product name containing a glyph you did not subset, and that character alone falls back to another typeface, mixing two faces inside one sentence. Fine for a landing page with fixed copy; risky for a site that keeps publishing.

A dynamic split cuts the Hangul range into many chunks and lets the browser take what it needs. There are more files, but each is small and any character is covered. For a site whose content grows, this is the right answer.

For a site that keeps publishing, the decision is not a close one

Preload only the first-screen chunks

With the split in place, the browser only requests a font after parsing CSS and meeting the text. For prominent first-screen text such as a hero heading, a preload is worth the head start.

<link rel="preload" as="font" type="font/woff2"
      href="/fonts/pretendard-korean-12.woff2" crossorigin>

Two traps here. First, without the crossorigin attribute the preload is wasted. Fonts are fetched in CORS mode even from your own origin, so omitting it makes the browser download the same file twice — the console reports it as a preloaded resource that was never used.

Second, preloading every chunk undoes the split. A preload says “fetch this unconditionally”, so limit it to the handful of chunks your first-screen copy genuinely touches. Change the copy and the list goes stale — the day you rewrite a hero headline, check the preload list again.

The relationship between asset weight and first paint is covered further in the Performance archive, and you can see what a site actually downloads using the diagnostics in our free tools.

Next part

However finely you split it, a font still takes time to arrive. What happens to the text in the meantime — invisible, or shown in another face — is decided by font-display, and that is the next part.

More on this topic

All technotes

Performance optimization Advanced

A front-end budget you can actually hold

Pages do not get heavy in one go. They get heavy through small, individually reasonable additions. A ceiling you actually check is what stops the accumulation.

Developers 8 min read

Performance optimization Advanced

A conversion audit: finding where people leave

Polishing a page nobody reaches is the most common waste. Walk the whole path end to end and the leak shows itself first.

Marketers 7 min read

Performance optimization Practical

The cost of pop-ups — search and conversion at once

An interstitial that covers the content on mobile is treated unfavourably in search, and the same interstitial costs conversions too. The levers are timing, trigger and dismissal.

Marketers 8 min read

₩270,000 · Join the program