Persian fonts and site speed
A Persian font is heavier than its Latin equivalent, but the biggest saving does not come from making the file lighter; it comes from not sending files that are not needed. This site writes five weights in its stylesheet and sends only three font files, 74.6 KB in total.
- Lesson 8 of 10
- Intermediate
- Free, no signup
Each weight, before and after subsetting
-
the ordinary weight, for body text
41.7 KB24.7 KB
the only weight nearly every page needs
-
the bold weight, for headings
41.8 KB24.9 KB
weight 600 also comes from this one and has no file of its own
-
the heaviest weight, for the main heading
42.0 KB25.0 KB
its existence stops a fake bold being fabricated
This shape shows only the three weights that get sent. The two other weights written in the stylesheet have no file at all and the browser picks them from these three, so the real saving is larger than this shape shows.
Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.
Why a Persian font costs more
A Latin font has one shape per letter. A Persian font has several shapes per letter, because a letter is written differently at the start, in the middle, at the end of a word and on its own, and the joined combinations are added on top of that. The result is a file with far more glyphs, and that is what drives its weight up.
The real number from this site own files: the main font at the ordinary weight carries 726 glyphs and 652 codepoints and is 41.7 KB. Its subset version carries 599 glyphs and 527 codepoints and is 24.7 KB.
Subsetting means removing the parts of a font this site will never need. It is the right thing to do and you should do it, but its craft is not this page subject: what a Persian font must keep so its letters do not fall apart is covered in detail in the Persian typography lesson and we do not repeat it here.
What we take up here is a different question, asked less often: once your file is light, how many of those files do you actually have to send? On this site the answer to that question saved more than subsetting did.

How many weights do you actually need?
This is where most Persian sites lose bytes, and the fix is not a setting: it is a rule inside CSS itself that almost nobody uses.
When the browser reaches text at a weight you have no file for, it does not leave that text without a font and it does not fabricate one; it picks the nearest weight you do have. The rule is written into the CSS standard and the search direction is defined: for a weight below five hundred, lower weights are checked first and higher ones after; for a weight above five hundred, higher first and lower after.
Now this site. Five weights are written in the main stylesheet and you can count them yourself:
grep -ohE 'font-weight:[[:space:]]*[0-9]{3}' style.css | tr -d ' ' | cut -d: -f2 | sort -unThe output is five numbers: 400, 500, 600, 700 and 800. But in the HTML this site actually serves there are only three font-face blocks: 400, 700 and 800. Weight 500 resolves to 400 and weight 600 to 700, because the rule above says so, and nobody sees the difference on the page.
Work out the saving. The three files that are sent are 24.7, 24.9 and 25.0 KB, 74.6 KB in total; the same three weights in full are 41.7, 41.8 and 42.0 KB. Had all five weights been sent in full without subsetting, it would have been 208.6 KB. Two decisions together removed more than two thirds of the font weight, and the second one, not sending two of the weights, was about 50 KB on its own: more than subsetting saves on a single file.
One more thing tied to the same rule: because 700 and 800 both have files, the browser is never forced to fabricate a bold. Fake bold looks worse on Persian script than on Latin, and the Persian typography lesson explains why.
And a small confession from our own unfinished tidying: the font folder of this site holds a sixth weight taking up 41.5 KB that nothing in the code refers to. It is never sent to a visitor, so it costs no speed, but it deserves deleting just the same.
font-display: why we chose optional and not swap
Until the font file arrives, the browser has to decide what to do with the text, and font-display writes that decision down. Almost every tutorial recommends swap. We chose optional and we write down why, because this is one of those places where the common advice works worse for Persian.
With swap the browser draws the text immediately in a fallback font and switches the moment the real font arrives. The text is never invisible, and that is this option win. Its price is precisely that moment of switching: letter widths change, lines re-break and the whole page jolts. That jolt is the layout shift measured in the Core Web Vitals lesson.
With optional the browser gives the font a short window. If it arrives in time the page is drawn with it from the start; if it does not, the fallback stays for that entire visit and no switch happens at all. Layout shift caused by fonts becomes definitively zero.
Why this choice matters more in Persian: the width difference between a Persian font and a system fallback is larger than the same difference in Latin. A paragraph that runs to six lines in the fallback may run to five in the real font, and that one line of difference moves everything below it on the page.
And now the price we paid, which should be said plainly: with optional, a first-time visitor on a slow connection may spend that entire visit reading the fallback and never see the font we chose. For us the trade was worth it, because on their second visit the font comes from cache and is right from then on. If seeing your brand font exactly on that first visit is vital to you, your choice differs, and that is defensible too.
swap and optional are two different trades
swap
- text is drawn at once in the fallback and is never invisible
- the real font swaps in whenever it arrives, even late
- at that moment of swapping, the page layout jolts
optional
- the font gets a short window and after it never swaps in
- layout shift caused by the font becomes definitively zero
- a visitor on a slow connection may never see your font
Neither is wrong. We chose optional because it makes layout shift definitively zero, and the price is that some visitors never see our font on their first visit.
Start the font sooner: preload and Early Hints
The browser does not know it needs a font until it has read the CSS, and it has no CSS until the HTML arrives. So the font download naturally starts late, and everything in this section is about starting it earlier.
The first step is preload: one line in the head telling the browser to start this file now. The rule most often ignored here is to preload only the fonts of the first view. Preload all five weights and the browser starts five simultaneous downloads, and bandwidth that should have gone to the main image of the page goes to a font needed at the bottom of it. This site has three fonts and preloads only two: the ordinary weight for body text and the heaviest weight for the main heading.
The second step is seen less often and its effect is real. The server can send that same preload as a response header, and some content delivery networks pass it to the browser as a preliminary response with the code 103, before the HTML itself is ready. So during the time the server spends building the page, the browser can be downloading the font.
On this site it works and it is visible from outside: a request to the front page first returns a 103 response carrying those same two fonts and then the 200 response with the page itself. To try it on your own site, take the headers with a command-line tool and look for a line starting with 103; the browser does not show this response in its developer tools.
And one condition that separates this from a decorative setting: 103 is only useful when building the page genuinely takes time. On a page served from cache that answers in twenty milliseconds, there is no head start left to gain.
Where the font starts and how it gets a head start
-
1
the 103 preliminary response
before the HTML is ready, the browser gets the font address
-
2
the preload line in the head
only the two fonts of the first view, not all five weights
-
3
the font-face block in the CSS
this is where the browser learns which weights exist at all
-
4
drawing the text
with the real font if it arrived, otherwise with the fallback stack
The first step only means something on a page that takes time to build. A page served from cache that answers in twenty milliseconds has no head start to gain.
A fallback stack that does not jump
Every visitor sees the first moment in a fallback font, and if you chose optional some of them see the whole visit in it. So this stack is not a decorative detail, it is part of the real experience and it should be chosen rather than copied from a template.
The stack this site uses runs in this order: the custom font, then a common open-source Persian font, then the system default font, then two faces known to exist on Windows, and finally sans-serif. The logic is simple: each rung moves towards something more likely to exist, and the last rung is something that always does.
Two things must not be forgotten when choosing a Persian stack. First, a font that carries only Latin is not merely useless in this stack, it is harmful: the browser moves to the next rung for Persian letters but may take that one for digits and Latin characters, and your text ends up written in two different fonts. Second, Tahoma carries Persian letters on Windows and that is why it still appears in most Persian stacks; it is not beautiful, but it exists, and existing is exactly what the last rungs are for.
And a thing worth doing that is done less often: actually test the stack. In your browser developer tools remove the name of the real font from the rule and look at the page. If it is still readable, the optional decision becomes easy; if it looks like a broken page, change your stack rather than your font-display.
And the last piece, which connects to the caching lesson: of all the files on a site, a font file is the best candidate for a long cache, because it never changes. On this site fonts are sent with a one-year lifetime, so a visitor who has been here once pays zero bytes for the font next time. That single setting makes everything above irrelevant from the second visit onward.
Do these
- count which weights you actually wrote in the stylesheet
- ship files only for weights the browser cannot resolve from the others
- preload only the fonts of the first view
- send fonts with a long cache lifetime; they never change
- look at the page once without the real font and check it reads
Do not do these
- preloading every weight you own
- putting a Latin-only font in a Persian fallback stack
- sending older formats alongside woff2
- subsetting from the characters visible on the page today
The fast path, with AI
The usual way to make a font lighter is uploading the file to an online service and taking the output. It works and it has two problems: the decision about what gets removed is not yours, and doing it again next time starts from scratch. The fast path has two steps and its first step is the one almost everybody skips: before making files lighter, work out which files are needed at all. The model has a good role here and that role is not deciding; it compares the list of weights in your stylesheet with the list of files you have and says which files the browser covers by itself. That comparison is exactly what saved about 50 KB on this site.
- Count the weights you wrote in the stylesheet. The command in the second section does exactly that and its output is a short list of numbers.
- Hand that list, together with the list of font files you have, to the model and ask which weights can be dropped and which remaining weight each one resolves to.
- Subset only the weights that survive. The standard tool for this is pyftsubset from the fontTools package, and in its default mode it keeps the layout features Persian script depends on.
- Check the output with your eyes rather than with a number. Look at one Persian paragraph, one heading, a few digits and one Latin word in the new font. If a letter has come away from the next one, the subset is broken, and the file size says nothing about that.
Copy-ready recipe
I want to reduce the number of font files my site sends without changing how it looks.
The weights written in the stylesheet:
{paste the output of the weight-counting command here}
The font files I have now, with sizes:
{paste the list of files here}
Rules:
1. Work from the font matching algorithm in the CSS standard, not from guesswork. For each weight whose file we drop, say which remaining weight the browser will pick and why.
2. If dropping a weight would make the browser fabricate a fake bold, do not drop it and say so.
3. Do not drop a weight merely because it is used once in the stylesheet; the criterion is how different the nearest remaining weight is.
4. Calculate the saving only from the file sizes I gave you and do not invent new numbers.
5. Do not promise anything about speed or rankings.
Give the output in three parts: the files that stay, the files that go and what each resolves to, and the total size before and after.
Before you trust the output: The model knows the font matching algorithm and has not seen your font, so two things remain yours to check. First, your font file may not be the weight its name claims; a filename guarantees nothing and the model is reading the filename. Second and more important, you have to look at the result: after dropping a weight, open a page where that weight is used and compare with your eyes. The difference between 600 and 700 shows on a large heading and does not show on a small label, and only looking settles that.
AI in this kind of work
In font work, AI does one job genuinely well and one job it should not touch at all. It is good at reasoning over rules: the font matching algorithm is written into the CSS standard and a model can run it over your list and say which weight resolves to which. What it should not do is produce or rebuild the font file itself. Our position: let the model touch the list, not the file.
Tools that actually help
- Claude Suited to that reasoning over rules: it takes the list of weights, runs the matching algorithm over it and holds to rules if you set them. Iran is not on the Anthropic supported-countries list, so there is no official signup and no Iranian card is accepted.
- Gemini Useful for looking at the page itself: hand it two screenshots of one page under two fonts and ask where the layout moved. That answer settles whether your fallback stack is close enough. Google own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.
- ChatGPT Enough for the same list work and for explaining the flags of a subsetting tool. We make no claim about access from Iran, because the OpenAI supported-countries page, like the rest of that domain, returns 403 to this server.
Where it backfires
Two risks, and the second belongs to Persian script specifically. First, invented flags: the model has seen the pattern of font tool commands and can write a flag that does not exist or that means something else in your version. The danger is higher here than elsewhere, because a broken font file does not appear as an error; the file gets built, it is even smaller, and only looking at the text tells you something is not where it should be. The second risk is that in its worst form: if the model tells you to strip layout features to save size, that instruction is harmless on Latin text and pulls Persian letters apart. Look at every subset output with a real Persian paragraph rather than with a file size. And a third thing you should simply not do: do not ask the model to make a font itself or to derive a new weight from an existing one; what comes out is a font nobody has tested and whose licence is unclear.
Sources: MDN: font-display Anthropic: supported countries Google: where Gemini Apps are available
Where this advice stops
This lesson is about the weight and loading of a font, not about choosing one; which Persian font is right for your brand and what makes a Persian font good is the job of the Persian typography lesson. It has three other boundaries. First, the numbers here belong to one font family and will differ on yours, especially the subsetting ratio, which depends on how many unnecessary characters the original carried. Second, choosing optional is our choice, and for a site whose visual identity depends on the font being seen exactly on that first visit the answer differs. Third, we have no first-hand experience of variable fonts on a Persian site; in theory they fit several weights into one file, but since we have not tried them on this site we make no claim about their size or their behaviour in Persian.
From our own work
The thing that helped us more than any setting in this job was a rule inside CSS itself, and we understood it from a measurement rather than from a tutorial. The stylesheet of this site writes five weights and for a while we were sending all five files. Then we counted the weights and compared them with the files, and saw that the browser goes to 400 for weight 500 and to 700 for weight 600, because the font matching algorithm says so. We deleted two files and nobody, ourselves included in a side-by-side comparison, saw any difference on the page. Those two files were about 50 KB, more than subsetting saves on a single file. The lesson that stayed with us is this: before making files lighter, count how many of them are needed at all.
Real follow-up questions
Should I serve the font from my own server or from an external service?
From your own server. The old argument for an external service was that the visitor might already have that font cached from another site, and that argument no longer holds because browsers now partition the cache per site. An external service now means one extra connection to another domain, and for an Iranian visitor it means one more point of failure.
Do I need any format besides woff2?
No, and sending older formats alongside it is a pure loss. Every browser genuinely in use today understands woff2 and it is also the most compressed format. If your site has several formats listed one after another in old code, deleting them is a safe tidy-up.
Should I use the system font and send no custom font at all?
For speed that is the best possible answer and it costs zero bytes, but in Persian the price is higher than in Latin. The default font differs a great deal between devices in Persian, so your page takes three different shapes on Android, Windows and iPhone. If visual identity does not matter to you and speed is the first priority, it is the right choice; otherwise one subset font in three weights is a balanced trade.