گزارش سرعت میگوید فونت دیر رسیده است. توسعهدهنده فوراً font-display: swap میگذارد و همه وزنها را preload میکند؛ متن زودتر دیده میشود، اما تیتر دو خط میشود، دکمه پرداخت جابهجا میشود و کاربر شبکه موبایل هنوز چندصد کیلوبایت فونت میگیرد. مشکل «فونت بد» نیست؛ مشکل این است که برای بارگذاری فونت وب قرارداد نداریم.
بهینهسازی واقعی از انتخاب یک ترفند شروع نمیشود. باید بدانیم کدام متن برای اولین نمایش حیاتی است، چه کاراکترها و وزنهایی واقعاً مصرف میشوند، فونت جایگزین چقدر با فونت اصلی هماندازه است، مرورگر فایل را چه زمانی کشف میکند و نتیجه در اینترنت و دستگاه کاربران ایرانی چیست. این راهنما همین مسیر را از Inventory تا WOFF2، Subset فارسی، font-display، Preload، CORS، Cache، CLS، RUM و Rollback به یک برنامه قابل آزمون تبدیل میکند.
خلاصه تصمیم: فونت سریع با «فایل کمتر و قرارداد روشن» ساخته میشود
| پرسش | پاسخ کوتاه | شاهد لازم |
|---|---|---|
| فونت سفارشی لازم است؟ | فقط اگر خوانایی، زبان یا هویت برند سود قابلاثبات دارد | تست خوانایی، Brand requirement و مقایسه System font |
| چه فرمتی؟ | برای مرورگرهای مدرن معمولاً WOFF2 | Support matrix واقعی پروژه |
| Static یا Variable؟ | براساس تعداد وزن/محور مصرفی، نه نام فناوری | حجم Transfer و تعداد Request در Route واقعی |
font-display کدام است؟ | براساس اهمیت نمایش فوری، الزام برند و ریسک Swap | FCP/LCP، CLS و مشاهده بصری |
| Preload چند فایل؟ | فقط فایل حیاتی و قطعی؛ گاهی صفر یا یک | Waterfall و نرخ استفاده همان فایل |
| Self-host یا سرویس ثالث؟ | هر دو را در شرایط کاربران مقایسه کنید | Connection، TTFB، Cache hit، Availability و Privacy |
| موفقیت چیست؟ | متن زود، پایدار و خوانا با هزینه انتقال کنترلشده | RUM تفکیکشده بر Route/Device/Network |
اگر فقط یک اصل به خاطر میسپارید: فایلی که درخواست نمیشود از هر فونت فشردهای سریعتر است. ابتدا خانواده، وزن، Style و Icon font اضافی را حذف کنید؛ سپس سراغ بهینهسازی فایلهای باقیمانده بروید.
مرز مقاله: بهینهسازی Font Delivery، نه انتخاب زیباییشناختی فونت
این مقاله مالک چرخه Discovery → Fetch → Display → Swap → Measure است. برای اندازه و Line-height، خوانایی فارسی، فاصلهگذاری، نیمفاصله و طراحی RTL به راهنمای تایپوگرافی و طراحی سایت فارسی مراجعه کنید. برای تعریف و عیبیابی کامل LCP، INP و CLS نیز راهنمای Core Web Vitals و RUM مرجع داخلی دقیقتر است.
هدف اینجا «کسب امتیاز ۱۰۰» یا حذف هویت بصری نیست. هدف ساخت قراردادی است که میان Readability، Brand، سرعت نمایش، ثبات چیدمان، پهنای باند، نگهداری و دسترسپذیری توازن قابلاندازهگیری ایجاد کند.
مرورگر فونت را چگونه کشف و رندر میکند؟
وجود یک @font-face بهتنهایی دانلود را آغاز نمیکند. مرورگر HTML و CSS را میخواند، DOM و CSSOM را میسازد، Style را به متن موجود تطبیق میدهد و فقط Face لازم برای کاراکتر، Weight، Style و Stretch مصرفشده را درخواست میکند. بنابراین فایل فونت ممکن است دیر برسد چون Style sheet دیر کشف شده، CSS با @import زنجیره ساخته، متن بعداً با JavaScript ظاهر شده یا Face واقعاً در صفحه استفاده نشده است. توضیح رسمی این چرخه و راهکارهای تحویل در راهنمای فونت web.dev آمده است.
- HTML و Style sheet کشف میشوند.
- قواعد
@font-faceو عناصر دارایfont-familyتطبیق مییابند. - مرورگر برای کاراکتر و Face موردنیاز یک منبع را انتخاب میکند.
- در دوره انتظار، رفتار نمایش با
font-displayو سیاست مرورگر تعیین میشود. - با رسیدن فونت، متن Paint میشود یا از Fallback به Web font تغییر میکند.
پس «فایل من فقط ۴۰KB است» تشخیص کافی نیست. Discovery دیرهنگام، اتصال ثالث، CORS نامعتبر، Preload تکراری، Mapping اشتباه Weight یا Swap پرهزینه میتوانند از حجم مهمتر باشند.
FOIT و FOUT چه هستند و به کدام معیار آسیب میزنند؟
| رخداد | آنچه کاربر میبیند | ریسک محتمل | تشخیص |
|---|---|---|---|
| FOIT | متن برای مدتی نامرئی است | تأخیر FCP/LCP متنی و دسترسی دیر به محتوا | Filmstrip و Paint timing |
| FOUT بدون جابهجایی | Fallback و سپس فونت اصلی، با هندسه نزدیک | تغییر بصری محدود | Filmstrip و Font panel |
| FOUT با جابهجایی | شکست خط، ارتفاع یا عرض عناصر تغییر میکند | CLS و کلیک اشتباه | Layout Shift attribution |
| فونت هرگز نمیرسد | Fallback دائمی | ناسازگاری برند یا افت خوانایی؛ نه الزاماً شکست کار | Network error و document.fonts |
فونت میتواند FCP یا LCP را عقب بیندازد و تفاوت Metricهای Fallback و Web font میتواند CLS بسازد؛ اما هر CLS یا LCP بدی از فونت نیست. تصاویر بیابعاد، Banner، Widget و JavaScript هم علتهای رایجاند. تعریف و روش اندازهگیری CLS در web.dev تأکید میکند که نتیجه Lab با تجربه تمام طول Session کاربران یکی نیست.
ممیزی قبل از تغییر: Font Inventory بسازید
قبل از حذف یا Preload، یک Route نماینده را با Cache خالی و سپس Cache گرم ثبت کنید. صفحه اصلی بهتنهایی کافی نیست؛ صفحه محصول، مقاله، Checkout، پنل و Landing ممکن است فونتهای متفاوت داشته باشند.
| فیلد Inventory | نمونه | سؤال تصمیم |
|---|---|---|
| Family/Face | Vazirmatn 400 normal | در کدام Element و Route استفاده میشود؟ |
| Source | Same-origin، CDN یا Provider | چند اتصال و Trust boundary دارد؟ |
| Transfer/Decoded | مثلاً 38KB/91KB | هزینه واقعی شبکه و حافظه چیست؟ |
| Discovery time | از HTML، CSS، JS یا Preload | چرا درخواست دیر آغاز شده است؟ |
| Use | Body، Heading، Icon، فقط Modal | Critical است یا Lazy/حذفشدنی؟ |
| Glyph/Axis | Persian+Latin، ۴۰۰/۷۰۰ | Subset یا Variable چه چیزی را کم میکند؟ |
| Display/Metric | swap + fallback mismatch | FOIT/FOUT/CLS چگونه کنترل میشود؟ |
| License/Owner | مجوز، نسخه و صاحب بهروزرسانی | Self-host و Subset مجاز و پایدار است؟ |
در DevTools ستون Initiator، Priority، Timing، Size و Response headers را ثبت کنید. Coverage میتواند CSS استفادهنشده را نشان دهد، اما بهتنهایی ثابت نمیکند یک Glyph یا Face در همه مسیرها بیمصرف است. Dynamic state، Locale و Login را هم آزمایش کنید.
مرحله صفر: آیا اصلاً Web font لازم است؟
System font دانلود ندارد و در Failure هم باقی میماند؛ در مقابل، فونت سفارشی میتواند زبان فارسی، خوانایی، هویت یا تمایز محصول را بهتر کند. انتخاب باید به Outcome متصل باشد.
| گزینه | مناسب وقتی | هزینه/ریسک |
|---|---|---|
| System stack | ابزار داخلی، متن کاربردی، Budget سخت یا برند مستقل از Typeface | تفاوت ظاهری میان OSها |
| یک Web font محدود | خوانایی فارسی و هویت مهم است، اما Weight کم لازم دارید | یک یا دو Request و نگهداری |
| Display + Body families | ارزش برند با تست اثبات شده است | Face بیشتر، احتمال Swap و پیچیدگی |
| Variable font | چند Weight/Width واقعاً در یک Family مصرف میشود | ممکن است برای یک Weight بزرگتر باشد |
برای درک اثر سرعت بر Task، تبدیل و هزینه، نه فقط SEO، راهنمای سرعت سایت، UX و کسبوکار را بخوانید. فونت وقتی ارزش دارد که سود خوانایی/برند از هزینه Delivery بیشتر باشد.
WOFF2 انتخاب پیشفرض مدرن است، نه مجوز برای نگهداشتن فایل اضافی
WOFF2 یک Recommendation رسمی W3C و Container فشرده برای داده فونت است. برای Support matrix مرورگرهای مدرن، معمولاً یک WOFF2 کافی است. افزودن WOFF، TTF و EOT بهصورت عادتی CSS را پیچیده میکند و اگر Source declaration اشتباه باشد حتی میتواند دانلود تکراری بسازد.
قاعده اجرایی:
- Browser support واقعی محصول را بنویسید؛ «همه مرورگرها» یک Requirement نیست.
- برای کاربران خارج از Support، Fallback خوانا داشته باشید.
- TTF/OTF منبع طراحی را بدون مجوز و Pipeline به فایل عمومی تبدیل نکنید.
- Compression انتقال مثل Brotli جای WOFF2 internal compression را نمیگیرد و برعکس.
- MIME type، CORS و Cache header را در Response واقعی بررسی کنید.
Static یا Variable: جمع حجم مصرفی را مقایسه کنید
Variable font چند Axis مانند Weight و Width را در یک فایل نگه میدارد. وقتی ۶ وزن و چند عرض مصرف میکنید، ممکن است تعداد Request و حجم مجموع را کم کند؛ وقتی فقط ۴۰۰ لازم است، یک Static subset میتواند کوچکتر باشد. راهنمای Variable fonts در web.dev نیز همین وابستگی به Use case را توضیح میدهد.
| سناریو | Candidate | آزمون |
|---|---|---|
| Body 400 + Heading 700 | دو Static subset یا Variable subset | Transfer مجموع و Render cost |
| Design system با ۱۰۰–۹۰۰ | Variable | Axis range واقعی و Browser matrix |
| یک Landing با یک وزن | Static | Route-specific bytes |
| Animation روی Axis | Variable با احتیاط | CPU/frame و Reduced Motion |
در تعریف Variable range را دقیق بنویسید؛ اگر فایل ۳۰۰ تا ۸۰۰ است، اعلام ۱۰۰ تا ۹۰۰ Mapping غلط ایجاد میکند.
@font-face {
font-family: "Brand Variable";
src: url("/fonts/brand-fa-latin-v4.woff2") format("woff2") tech("variations");
font-weight: 300 800;
font-style: normal;
font-display: swap;
}Subset فارسی: کمکردن Glyph بدون شکستن شکلدهی متن
Subset فقط حذف «زبانهای اضافی» نیست. خط فارسی برای شکل اتصال حروف، Ligature، Mark positioning، اعراب، نیمفاصله، اعداد، نشانهها و متن Mixed فارسی/لاتین به جدولهای OpenType وابسته است. خروجی کوچک اما معیوب میتواند «ی»، «ک»، فاصله، اعراب یا اتصال را در دستگاهی خاص خراب کند.
مجموعه آزمون فارسی را قبل از Subset ثابت کنید
- حروف فارسی و عربی رایج، «ی/ی» و «ک/ک» در ابتدا، وسط و انتهای واژه؛
- نیمفاصله U+200C در «میشود»، جمعها و پسوندها؛
- اعداد فارسی، عربی و لاتین، ممیز، درصد، قیمت و تاریخ؛
- اعراب، تشدید، همزه، پرانتز، گیومه، خط تیره و Ellipsis؛
- متن Mixed مانند «نسخه v2.۴ برای API فارسی» در RTL؛
- Bold/Italic واقعی، لینک، Button، Input، PDF preview و Canvas اگر مصرف دارید.
ابزار Subset باید Glyphها و جدولهای GSUB/GPOS لازم را حفظ کند. یک Snapshot بصری از متن معیار در Chromium، Firefox، Safari/WebView و Android کمقدرت بگیرید. مجوز فونت را نیز پیش از Self-host، تغییر، Subset یا تبدیل بررسی کنید.
unicode-range برای Split هوشمند است، نه Decorator
Descriptor unicode-range به مرورگر میگوید یک Face برای چه Code pointهایی قابل استفاده است و میتواند Persian و Latin را در فایلهای جدا تحویل دهد. اما Range ناقص، همپوشانی مبهم یا Preload یک Subset غیرلازم نتیجه عکس میدهد. Range را از Character corpus واقعی تولید، نسخهبندی و با صفحات دوزبانه تست کنید؛ از کپی Range نامرتبط از اینترنت بپرهیزید.
قرارداد درست @font-face: هر Face دقیقاً چه چیزی ارائه میکند؟
مرجع @font-face در MDN Descriptorهای Source، Weight، Style، Stretch، Display، Unicode range و Metric override را توضیح میدهد. تعریف حداقلی زیر برای یک فایل Static نمونه است:
@font-face {
font-family: "Product Sans";
src: url("/assets/fonts/product-sans-fa-latin-400.v3.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Product Sans";
src: url("/assets/fonts/product-sans-fa-latin-700.v3.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
body {
font-family: "Product Sans", Tahoma, Arial, sans-serif;
}Weight و Style را جعل نکنید
اگر فقط Face ۴۰۰ تعریف شود اما CSS از ۷۰۰ استفاده کند، مرورگر ممکن است Bold مصنوعی بسازد؛ کیفیت و عرض متن میتواند متفاوت شود. Faceها را با مصرف واقعی Map کنید و در صورت نیاز font-synthesis را آگاهانه کنترل کنید. نام فایل، Descriptor و CSS component باید یک Truth مشترک داشته باشند.
local() همیشه بهینهسازی بیخطر نیست
local() میتواند دانلود را حذف کند، ولی نسخه نصبشده کاربر شاید Glyph، Metric یا Feature متفاوتی داشته باشد و خروجی غیرقابلتکرار بسازد. برای ابزار عمومی یا محصولی که Pixel consistency مهم است، حذف local() میتواند تصمیم درستتری باشد. آن را فقط با Requirement و تست چند نسخه بهکار ببرید.
font-display را براساس Priority انتخاب کنید
font-display دوره Block و Swap را کنترل میکند، اما مدت دقیق و رفتار نهایی میتواند به Browser وابسته باشد. بنابراین «swap همیشه بهترین است» یا «optional فقط تزئینی است» قانون عمومی نیست. تعریف رسمی گزینهها در مرجع MDN برای font-display قابل بررسی است.
| گزینه | هدف | ریسک | Candidate |
|---|---|---|---|
swap | متن فوری و Web font در نهایت | Swap دیر و CLS اگر Metric ناهماهنگ باشد | متن محتوایی با Fallback خوب |
optional | نمایش سریع و پذیرش Fallback در اتصال کند | Brand font ممکن است در Visit جاری اعمال نشود | Body با Performance priority بالا |
fallback | Block کوتاه و Swap محدود | رفتار میانی نیازمند تست | برند با تحمل محدود Swap |
block | نمایش با Face خاص | متن نامرئی و تأخیر Paint | استثنای محدود؛ نه Body عمومی |
auto | واگذاری به Browser | کنترل و پیشبینی کمتر | وقتی Default آگاهانه پذیرفته شده |
یک سایت میتواند بیش از یک سیاست Display داشته باشد
Body، Logo text، Heading و Code block نقش یکسان ندارند. ممکن است Body با optional و Display heading با swap بهتر باشد. نتیجه را با Fallback سرد، Cache گرم، Slow network و Repeat view مقایسه کنید. Icon font را با این سیاستها درمان نکنید؛ جایگزینی Glyph آیکن با حرف یا مربع میتواند معنای کنترل را عوض کند.
کاهش CLS با Metric-compatible Fallback
swap متن را زود نشان میدهد، اما اگر Fallback عرض و ارتفاع متفاوتی داشته باشد، شکست خط و ارتفاع Card عوض میشود. راهحل فقط «فونت زودتر» نیست؛ باید Metricهای Fallback را به Web font نزدیک کنید.
- Fallback رایج روی دستگاههای هدف را انتخاب کنید، نه فونتی که فقط روی لپتاپ طراح نصب است.
- Line-height و Container را طوری طراحی کنید که تفاوت کوچک، CTA و قیمت را جابهجا نکند.
size-adjust،ascent-override،descent-overrideوline-gap-overrideرا با Metric استخراجشده بسازید.- Browser support هر Descriptor را در Support matrix پروژه بررسی و Progressive enhancement تعریف کنید.
CSS Fonts در W3C توضیح میدهد که size-adjust Outline و Metric را Scale میکند. درصدها را حدس نزنید؛ از ابزار Metric یا اندازهگیری متن مرجع استخراج و Visual regression کنید.
/* اعداد زیر فقط ساختار را نشان میدهند؛ برای فونت خودتان اندازهگیری کنید. */@font-face {
font-family: "Product Fallback Tuned";
src: local("Tahoma");
size-adjust: var(--measured-size-adjust);
ascent-override: var(--measured-ascent);
descent-override: var(--measured-descent);
line-gap-override: var(--measured-line-gap);
}
body {
font-family: "Product Sans", "Product Fallback Tuned", Tahoma, sans-serif;
}Preload: بدهی Priority فقط برای فونت قطعی و حیاتی
Preload مرورگر را وادار میکند فایل را پیش از تطبیق کامل CSS با اولویت دریافت کند. اگر فایل در Route استفاده نشود، Subset دیگری لازم باشد یا تصویر LCP مهمتر باشد، این Hint پهنای باند را از منبع مهمتر میگیرد. web.dev نیز در راهنمای Preload فونت بر احتیاط و اثر آن بر unicode-range تأکید دارد.
<link
rel="preload"
href="/assets/fonts/product-sans-fa-latin-400.v3.woff2"
as="font"
type="font/woff2"
crossorigin
>چهار شرط Preload موفق
- URL، Format و Credentials mode دقیقاً با درخواست CSS یکسان باشد.
- فایل در بخش اولیه همان Route با احتمال بسیار بالا مصرف شود.
- Waterfall نشان دهد Discovery فعلی دیر است و اصلاح CSS کافی نیست.
- آزمایش ثابت کند LCP/FCP بهتر و منابع مهم دیگر بدتر نشدهاند.
Warning «preloaded but not used» را نادیده نگیرید. Preload همه Weightها، فایل Latin در صفحه فارسی یا فایل Mobile/Desktop متفاوت میتواند Waste بسازد. Budget را از صفر شروع کنید و برای هر Hint Owner و Removal condition بنویسید.
CSS Discovery را اصلاح کنید؛ Preload نباید عیب معماری را پنهان کند
اگر @font-face در CSS دیررس یا زنجیره @import است، اول Discovery را کوتاه کنید. Critical declaration کمحجم میتواند در Head باشد؛ اما Inline کردن خود فایل فونت معمولاً HTML را بزرگ و Cache مستقل را نابود میکند.
- Style sheet حیاتی را زود و بدون Redirect تحویل دهید.
@importچندلایه را حذف کنید.- CSS و Faceهای بلااستفاده Route را Split یا حذف کنید.
- فونت Modal یا Editor را با بازشدن Feature بارگذاری کنید، نه در همه صفحات.
- CSS-in-JS و Hydration را بررسی کنید تا Font rule پس از JavaScript ظاهر نشود.
Self-host یا Provider: «همان Origin» تضمین سرعت نیست
Self-host اتصال ثالث را حذف و کنترل فایل، Subset، Cache، Availability و Privacy را بیشتر میکند؛ اما Origin کند، CDN ضعیف، Header بد یا فایل بهینهنشده میتواند از Provider کندتر باشد. راهنمای رسمی web.dev نیز نتیجه را وابسته به CDN، Protocol و اجرای واقعی میداند، نه یک حکم قطعی.
| معیار | Self-host | Third-party |
|---|---|---|
| Connection | ممکن است اتصال موجود Reuse شود | DNS/TCP/TLS یا QUIC جدا |
| کنترل Subset/Version | بالا؛ مسئولیت با تیم | وابسته به API/Provider |
| Cache/CDN | کیفیت زیرساخت خودتان | کیفیت و Coverage ارائهدهنده |
| Privacy/Policy | Trust boundary کمتر | درخواست و سیاست ثالث باید بررسی شود |
| Availability ایران | وابسته به مسیر CDN/هاست شما | نیازمند Probe چند ISP و بررسی Eligibility |
| Maintenance | مجوز، Patch، Pipeline و Cache busting با شما | بخشی با Provider؛ Dependency بیشتر |
برای سرویس ثالث در صورت اثبات نیاز، preconnect به Originهای واقعاً حیاتی میتواند Connection setup را جلو بیندازد؛ ولی Preconnect اضافی هم Socket و انرژی مصرف میکند. نتیجه را در تهران، شهر دوم، اینترنت ثابت و موبایل، بدون VPN و در صورت مرتبط بودن با مسیرهای رایج کاربران ثبت کنید.
CORS، MIME، Cache و Versioning: فونت باید مثل Asset تولیدی اداره شود
فونت Cross-origin به CORS معتبر نیاز دارد. خطای Console یا Status ۲۰۰ بهتنهایی کافی نیست؛ ممکن است Response دانلود شود ولی بهدلیل Policy قابل استفاده نباشد. برای Same-origin هم crossorigin در Preload را با درخواست نهایی همسان کنید تا Cache entry جدا و دانلود دوباره نسازید.
| کنترل | انتظار | Failure رایج |
|---|---|---|
| URL versioned | Hash یا نسخه در نام فایل | جایگزینی فایل زیر URL قدیمی |
| Cache-Control | Long-lived برای Asset immutable | TTL کوتاه یا no-cache ناخواسته |
| CORS | Origin policy محدود و سازگار | Wildcard/credential mismatch یا Header غایب |
| Content-Type | نوع درست WOFF2 | Generic binary و رفتار ناسازگار |
| Compression | بدون Processing تکراری بیفایده | فرض اینکه gzip فایل مشکل Glyph را حل میکند |
| CDN purge | Version جدید و Rollback روشن | Purge همهچیز و Origin stampede |
برای طراحی TTL، Revalidation و نسخهگذاری به راهنمای معماری Cache وبسایت و برای مسیرهای ایرانی به راهنمای انتخاب CDN برای ایران مراجعه کنید.
Icon font را با فونت متن یکی نگیرید
در Icon font، Fallback ممکن است حرف، مربع یا نماد بیمعنا نشان دهد. این مسئله فقط CLS نیست؛ نام و معنای کنترل میتواند عوض شود. برای مجموعه محدود آیکن، SVG inline یا Sprite معمولاً کنترل رنگ، اندازه، Accessibility و Subset روشنتری میدهد.
- دکمه فقط آیکن باید Accessible name داشته باشد.
- آیکن تزئینی از Accessibility tree پنهان شود.
- اگر Icon font موقتاً باقی است، تمام Glyphها و Failure state تست شوند.
- برای «یک آیکن» کل کتابخانه صدها Glyph را بار نکنید.
فونت فارسی و RTL: Performance بدون خوانایی شکست است
کوچککردن فایل نباید کیفیت زبان را قربانی کند. فونت را روی Paragraph طولانی، Heading، عدد قیمت، جدول، Form، Placeholder، Error و Mixed text بررسی کنید. Line-height کم، Weight بسیار نازک یا تفاوت زیاد Fallback میتواند تجربه را حتی با LCP خوب خراب کند.
| سطح آزمون | نمونه ایرانی | شکست قابلمشاهده |
|---|---|---|
| پرداخت | ۱٬۲۵۰٬۰۰۰ تومان، کد OTP | عدد مبهم، جابهجایی دکمه، بریدگی |
| محتوا | مقاله طولانی با لینک لاتین | خستگی، Line break بد، Bidi confusion |
| فروشگاه | نام مدل فارسی/English | عرض Card و قیمت ناپایدار |
| فرم | ایمیل، موبایل و پیام خطا | Clipping، Fake bold یا Cursor نامنظم |
| دستگاه | Android قدیمی و WebView | Fallback متفاوت یا Glyph missing |
دسترسپذیری: کاربر باید بتواند متن را تغییر دهد
فونت زیبا نباید Zoom، Reflow یا Text spacing را بشکند. WCAG ۲.۲ درباره Text Spacing میخواهد محتوا با تغییر فاصله خط، پاراگراف، حرف و کلمه بدون از دسترفتن محتوا یا کارکرد باقی بماند. فونت را با ۲۰۰% Zoom، افزایش Text size، Contrast mode و User stylesheet تست کنید.
- متن را داخل تصویر فونتمانند نکنید.
- وزن نازک را روی نمایشگر کمکیفیت و نور زیاد تست کنید.
- Fallback باید همه Glyphهای ضروری را داشته باشد.
- ارتفاع Fixed برای Button و Input ممکن است با Zoom یا Fallback متن را ببرد.
- Icon و Ligature نباید تنها حامل اطلاعات باشند.
برای معیارهای گستردهتر، راهنمای طراحی فراگیر و ممیزی عملی دسترسپذیری وب را به کار ببرید.
CSS Font Loading API: برای نیاز خاص، نه جایگزین CSS ساده
CSS Font Loading API در MDN امکان Load و پایش Faceها با document.fonts را میدهد. برای Canvas، Editor، نمودار یا Featureای که باید بداند Font آماده است مفید است؛ اما اضافهکردن JavaScript برای Body text معمولاً پیچیدگی و Failure mode بیشتری میسازد.
const sample = "16px Product Sans";
if (!document.fonts.check(sample, "متن فارسی ۱۲۳")) {
document.fonts.load(sample, "متن فارسی ۱۲۳")
.then(() => performance.mark("critical-font-ready"))
.catch(() => performance.mark("critical-font-failed"));
}document.fonts.ready را برای Block کردن کل UI استفاده نکنید. Timeout، Failure، Offline و Fallback باید جزئی از قرارداد باشند. Telemetry نام فایل یا داده شخصی لازم ندارد؛ زمان و Outcome کافی است.
وردپرس: سه منبع متداول دانلود تکراری فونت
در وردپرس ممکن است Theme، Page builder و Plugin هرکدام یک Family یا همان Family را از URL متفاوت تعریف کنند. Local-font plugin هم ممکن است Google CSS را محلی کند اما نسخه قدیمی، Weight اضافی یا Preload همگانی بسازد.
- در HTML و CSS برای
fonts.،@font-faceو پسوندهای.woff2/.woffجستوجو کنید. - Initiator هر Request را به Theme/Plugin/Block نسبت دهید.
- یک Owner برای Font manifest تعیین کنید و تعریف تکراری را حذف کنید.
- Cache plugin را پس از تغییر Purge و HTML عمومی را با Login خارج بررسی کنید.
- Editor، WooCommerce cart/checkout، Popup و زبان دوم را جدا تست کنید.
Minify یا Combine کور میتواند ترتیب CSS و URL را تغییر دهد. قبل و بعد از هر Optimization، Network، Computed font و صفحه عمومی را ثبت کنید. چک فنی گستردهتر در چکلیست سئو فنی از Staging تا انتشار آمده است.
اندازهگیری Lab: Waterfall، Filmstrip و CPU را کنار هم ببینید
| ابزار/نما | پاسخ میدهد | محدودیت |
|---|---|---|
| Network waterfall | Discovery، Priority، Connection، Transfer و Cache | نمیگوید کاربر Shift را چگونه تجربه کرد |
| Performance/Filmstrip | FOIT/FOUT، Paint و Layout shift | یک دستگاه و اجرای مصنوعی |
| Rendering/Layout regions | عنصر جابهجا و زمان آن | علت Metric mismatch را باید تحلیل کرد |
| Coverage | CSS/JS مصرفشده در همان State | همه Routeها و Stateها را پوشش نمیدهد |
| WebPageTest/Lighthouse | مقایسه تکرارپذیر سناریو | RUM و Session کامل نیست |
هر آزمایش حداقل با Cold cache و Warm cache، Mobile viewport، CPU/Network محدود و سه تا پنج تکرار اجرا شود. Median و Tail را ببینید؛ یک Screenshot خوب شاهد کافی نیست.
RUM: نتیجه فونت را در کاربران واقعی بسنجید
Core Web Vitals را در صدک ۷۵ و به تفکیک Route، Device، Connection، Browser و Version فونت دنبال کنید. برای نسبتدادن تغییر، Version asset و Experiment cohort را در Telemetry غیرشخصی نگه دارید.
| SLI | تعریف عملی | Guardrail |
|---|---|---|
| Font ready | زمان آمادگی Face حیاتی از Navigation | Failure/Timeout rate |
| Font bytes | Transfer مجموع فونت در Route | Cache-hit و Repeat view |
| Font requests | تعداد Faceهای Fetchشده | Duplicate/preload waste |
| LCP/FCP | Render milestone واقعی | تصویر و JS بهعنوان Confounder |
| CLS attribution | Shiftهای مرتبط با Text container | Session کامل و Post-load |
| Outcome | Task completion، Read depth یا Conversion | Error، Accessibility و Bounce |
اگر Font bytes کم شد اما LCP ثابت ماند، پروژه لزوماً شکست نخورده؛ شاید پهنای باند ذخیره شده یا Font bottleneck نبوده است. اگر LCP بهتر و CLS بدتر شد، Optimization ناقص است. تصمیم باید چندمعیاره باشد.
راهنمای عیبیابی از نشانه تا علت
| نشانه | فرضیهها | آزمایش بعدی |
|---|---|---|
| متن دیر ظاهر میشود | block/auto، CSS دیر، Font بزرگ یا اتصال کند | Filmstrip + Initiator + Display policy |
| فونت دو بار دانلود میشود | Preload mismatch، URL متفاوت، CORS mode یا CSS تکراری | Request URL/Initiator/Cache key diff |
| CLS پس از رسیدن فونت | Metric mismatch، Fake weight، line-height یا Container تنگ | Fallback forced + Shift attribution |
| فقط بعضی حروف مربعاند | Subset ناقص یا Fallback glyph ندارد | Code point + Font panel + corpus |
| Bold دانلود جدا و ناگهانی | Face دیر مصرف یا Mapping غلط | Computed weight و Route state |
| Self-host کندتر شد | Origin/CDN/TTL بد، فایل بزرگ یا HTTP setup | Connection/TTFB/Transfer مقایسهای |
| Preload هشدار unused | Face در Fold مصرف نیست یا Unicode mismatch | Coverage و Remove experiment |
Font Budget و SLO: عدد جادویی ندهید، سقف محصول بسازید
یک Budget عمومی مثل «هر فونت زیر ۱۰۰KB» کافی نیست. Budget را بر Route و User journey تعریف کنید:
- حداکثر تعداد Request فونت در First view و Repeat view؛
- حداکثر Transfer برای Route اصلی در Locale فارسی؛
- فهرست Faceهای Critical و شرط Preload هرکدام؛
- حداکثر نرخ Font failure و Duplicate request؛
- Guardrailهای LCP/CLS و Accessibility؛
- Owner، تاریخ اندازهگیری و Support matrix.
Baseline را از داده امروز بگیرید، هدف بهبود مرحلهای تعیین کنید و Budget را در CI یا Performance review کنترل کنید. عدد بدون Context شبکه، Route و برند فقط حس قطعیت میسازد.
ماتریس آزمون برای کاربران ایرانی
| بعد | حداقل پوشش | چرا |
|---|---|---|
| شبکه | ثابت، موبایل، Latency/packet loss مصنوعی | Font حساس به Discovery و Tail latency است |
| مکان/مسیر | تهران + یک شهر دیگر + مسیر CDN مرتبط | Provider/Peering یکسان نیست |
| دستگاه | Android کمقدرت، iPhone/Safari، Desktop | Decode/Render و Fallback فرق دارد |
| Browser | Chromium، Firefox، Safari/WebView در Support | Display و Metric support متفاوت است |
| Cache | Cold، Warm، بازگشت بعد از Deploy | First/Repeat experience متفاوت است |
| متن | فارسی، Mixed، عدد/قیمت، نیمفاصله، اعراب | Subset و Bidi را آشکار میکند |
| State | Home، Article، Product، Checkout، Error | Faceهای Lazy و Pluginها متفاوتاند |
VPN را اگر بخش قابلتوجهی از کاربران دارد بهعنوان Segment ثبت کنید، نه اینکه نتیجه آن را به کل ایران تعمیم دهید. Provider eligibility، پرداخت، SLA و امکان دریافت فایل در شرایط اختلال را با تاریخ Snapshot مستند کنید.
مهاجرت کمریسک: از Shadow test تا Rollback
- Baseline: Manifest، Waterfall، CWV/RUM، Screenshot و Font corpus فعلی را ذخیره کنید.
- Build: WOFF2/Subset/Variable candidate را با License و Hash تولید کنید.
- Visual QA: Glyph، Metric، Weight، RTL، Form و Checkout را در Matrix تست کنید.
- Shadow: فایل را بدون اعمال عمومی از مسیر CDN/Origin واقعی Fetch و Headerها را بررسی کنید.
- Canary: یک Route یا Cohort کوچک با Version روشن منتشر کنید.
- Observe: Font ready/bytes/failure، LCP، CLS، Error و Outcome را مقایسه کنید.
- Expand: فقط با Guardrail سالم؛ سپس Face قدیمی را پس از دوره امن حذف کنید.
- Rollback: CSS manifest قبلی و فایل Versioned باید فوراً قابل بازگشت باشد.
فایل جدید را روی URL قدیمی Overwrite نکنید؛ Cacheهای مختلف ممکن است CSS جدید را با Font قدیمی ترکیب کنند. Versioning محتوامحور و انتشار ترتیبدار، Reconciliation را ساده میکند.
برنامه ۳۰روزه بهینهسازی فونت
| بازه | خروجی | شرط عبور |
|---|---|---|
| روز ۱–۵ | Inventory، Baseline، Route/Device matrix و Owner | تمام Faceها به Initiator و مصرف نسبت داده شدهاند |
| روز ۶–۱۰ | حذف Duplicate/Unused، WOFF2 و Candidateهای Subset/Variable | Corpus فارسی و License پاس شده است |
| روز ۱۱–۱۵ | Display policy، Fallback metric و Discovery fix | FOIT/CLS در Lab کنترل شده است |
| روز ۱۶–۲۰ | Preload/CORS/Cache/CDN و WordPress integration | دانلود تکراری و Header failure صفر است |
| روز ۲۱–۲۵ | Canary، RUM dashboard و Accessibility QA | Guardrailهای CWV/Task/Error سالماند |
| روز ۲۶–۳۰ | Rollout، Runbook، Budget و Removal plan | Rollback drill و تصمیم ثبت شده است |
خطاهای رایج که باید حذف شوند
- Preload کردن تمام Weightها «برای اطمینان»؛
- اعلام
swapبهعنوان جواب ۹۵ یا ۹۹ درصد سایتها بدون آزمون؛ - فرض اینکه Self-host همیشه سریعتر، امنتر یا خصوصیتر است؛
- Subset فارسی بدون نیمفاصله، اعداد، GSUB/GPOS و Visual regression؛
- تعریف یک فایل ۴۰۰ برای تمام Weightها و پذیرش Fake bold؛
- Variable font برای فقط یک Weight بدون مقایسه حجم؛
- استفاده از Icon font بزرگ برای چند آیکن؛
- تمرکز بر Lighthouse و ندیدن RUM، Repeat view و Session CLS؛
- تعویض فایل زیر URL ثابت بدون Version و Rollback؛
- نسبتدادن هر LCP/CLS بد به فونت بدون Attribution.
چکلیست انتشار فونت وب
- Family/Weight/Style/Stretch مصرفی Inventory شده است.
- فونت یا Face غیرضروری حذف شده و WOFF2 با Support matrix سازگار است.
- Static/Variable با Transfer واقعی Route مقایسه شده است.
- مجوز Self-host/Subset/Modification بررسی شده است.
- Corpus فارسی، نیمفاصله، عدد، اعراب و Mixed text پاس شده است.
@font-faceو Fallback mapping دقیق و بدون Synthesis ناخواستهاند.font-displayبرای نقش متن انتخاب، نه برای کل سایت کپی شده است.- Fallback metric، Zoom و Text spacing تست شدهاند.
- Preload فقط با شاهد Waterfall و بدون unused/duplicate باقی مانده است.
- CORS، Content-Type، Cache-Control، URL version و CDN پاسخ درست دارند.
- Cold/Warm، Route/Browser/Device/Network و Failure آزمایش شدهاند.
- RUM، Budget، Owner، Canary و Rollback ثبت شدهاند.
پرسشهای متداول درباره بهینهسازی فونت وب
بهترین مقدار font-display چیست؟
مقدار واحدی برای همه Faceها وجود ندارد. swap متن را سریع نشان میدهد اما با Fallback ناهماهنگ میتواند CLS بسازد؛ optional Performance را در اولویت میگذارد اما ممکن است Web font در همان بازدید اعمال نشود. نقش متن، الزام برند، زمان رسیدن فایل و Metricهای Fallback را در کاربران واقعی مقایسه کنید.
آیا Self-host کردن فونت همیشه سریعتر از Google Fonts یا CDN است؟
خیر. Self-host اتصال ثالث را حذف و کنترل را بیشتر میکند، اما سرعت به Origin/CDN، Protocol، Cache، Subset، TTFB و مسیر شبکه وابسته است. دو Candidate را با فایل یکسان، Cache سرد و گرم و ISPهای واقعی مقایسه کنید؛ Privacy، مجوز و نگهداری را نیز جدا بسنجید.
چند فایل فونت را Preload کنیم؟
از صفر شروع کنید. فقط فایلی را Preload کنید که در بخش اولیه همان Route قطعاً مصرف میشود، دیر کشف میشود و آزمایش نشان میدهد منبع مهمتری را عقب نمیاندازد. در بسیاری از صفحات صفر یا یک فایل کافی است؛ عدد ثابت برای همه سایتها معتبر نیست.
چگونه CLS ناشی از فونت را کم کنیم؟
ابتدا با Layout Shift attribution ثابت کنید فونت علت است. سپس Fallback نزدیک انتخاب، Metricها را با size-adjust و Overrideهای اندازهگیریشده هماهنگ، Line-height و Container را مقاوم و Font delivery را زودتر کنید. نتیجه باید در Lab و RUM، Cold و Warm cache تأیید شود.
بهترین فرمت و روش Subset برای فونت فارسی چیست؟
برای مرورگرهای مدرن معمولاً WOFF2 مبناست. Subset باید از Corpus واقعی فارسی/لاتین تولید و نیمفاصله، اعداد، نشانهها، اعراب و جدولهای OpenType لازم برای اتصال و جایگذاری را حفظ کند. فایل کوچکتر اگر یک Glyph یا شکلدهی را بشکند Optimization موفقی نیست؛ مجوز و Visual regression الزامیاند.
جمعبندی: فونت را بهعنوان قرارداد محصول اداره کنید
بهینهسازی فونت با یک Plugin، یک Preload یا یک مقدار font-display تمام نمیشود. قرارداد موفق مشخص میکند چه Faceهایی لازماند، هر فایل چه Glyph/Axis دارد، چه زمانی کشف میشود، در تأخیر یا شکست چه Fallbackی دیده میشود، چگونه Cache و نسخهگذاری میشود و کدام SLI نتیجه را ثابت میکند.
ترتیب عملی این است: حذف → WOFF2/Subset → Mapping درست → Display/Fallback metric → Discovery/Preload → Cache/CORS/CDN → Lab/RUM → Canary/Rollback. با این ترتیب، کاربر ایرانی متن را زودتر و پایدارتر میبیند، بدون اینکه خوانایی فارسی، دسترسپذیری یا امکان بازگشت را قربانی یک امتیاز مصنوعی کنیم.






