Arabic-script fonts are secretly huge

Latin webfonts ship a few hundred glyphs and everyone obsesses over their size. Then an Arabic-script font arrives with 1,000–4,000+ glyphs, dozens of required ligatures, per-position shaping forms — and the same people ship it as one 400 KB file on a page with three lines of Urdu on it.

The pattern holds across our library (sizes are WOFF2 where available, from the actual hosted files):

• A compact Naskh such as Noto Naskh Arabic : modest, UI-grade weight. • Amiri with its calligraphic ligature set: multiple times heavier. • Noto Nastaliq Urdu : the heaviest class in the library — the shaping engine needs a glyph for practically every word-shape combination approach.

Heaviness is not a flaw; it is the cost of correct Arabic-script typography. The fix is not smaller fonts — it is not loading what the page does not use .

unicode-range: the one-line win

@font-face accepts a unicode-range descriptor; the browser downloads the font only if the page contains a character inside that range :

The ranges that matter for Arabic-script work:

Range What lives there --- --- U+0600-06FF Arabic (core letters, harakat, Urdu/Persian/Sindhi/Pashto additions) U+0750-077F Arabic, Supplement (some Sindhi/Pashto letters) U+08A0-08FF Arabic, Extended-A U+FB50-FDFF Arabic Presentation Forms-A (ligatures, contextual forms) U+FE70-FEFF Arabic Presentation Forms-B (positional forms)

If your page mixes Latin and Arabic-script text (most Urdu sites do), give each script its own face with its own range — the browser loads only what renders.

Fallbacks that do not wreck your metrics

font-display: swap paints fallback text immediately and swaps when the webfont arrives. For heavy fonts that means a visible reflow — mitigate it:

1. Name a real Arabic-script fallback (a system Naskh) before generic serif , so the swap is Naskh → Naskh, not tofu → Nastaliq. 2. Reserve space: set the text container's min-height for the final font's metrics. Layout shift damages your Core Web Vitals more than a slow font does. 3. For hero text only, consider font-display: optional with preload — the font arrives with the document, no swap at all.

Subsetting: the sharp knife

You can physically subset the font file — strip every glyph the project will never render (other languages' letters, huge ligature runs). Tools: pyftsubset from fontTools (what our own pipeline uses), or a hosted service.

Two warnings before you cut:

• Subsetting is per-project, not per-font. A subset that serves a Quran site drops letters a Sindhi site needs. Our coverage benchmark shows exactly which letters any font carries — subset against your language's required set, never by eyeball. • Never subset away presentation forms blindly. Some rendering stacks still hit Forms-A/B for legacy content; test shaped output after every subset.

A middle path that usually wins: subset to the Unicode blocks you need (keep whole blocks), rather than to individual codepoints. You keep correct shaping for everything the page can actually contain.

What we do on this site

Every font page previews its font from the real hosted file; the @font-face snippet on each page (see the CSS generator) already carries font-display: swap and the Arabic unicode-range . The generator — /tools/css-generator — writes production-ready snippets including preload hints, so the checklist above is a copy-paste, not a research project.

The 5-point ship checklist

1. WOFF2 only (it is 2026; WOFF/TTF fallbacks are dead weight) 2. unicode-range on every Arabic-script @font-face 3. Real fallback font named before serif 4. Space reserved for the swap ( min-height on text containers) 5. preload for the one font above the fold, nothing more