بهینه‌سازی فونت وب؛ WOFF۲، Preload، CLS و فونت فارسی

گزارش سرعت می‌گوید فونت دیر رسیده است. توسعه‌دهنده فوراً font-display: swap می‌گذارد و همه وزن‌ها را preload می‌کند؛ متن زودتر دیده می‌شود، اما تیتر دو خط می‌شود، دکمه پرداخت جابه‌جا می‌شود و کاربر شبکه موبایل هنوز چندصد کیلوبایت فونت می‌گیرد. مشکل «فونت بد» نیست؛ مشکل این است که برای بارگذاری فونت وب قرارداد نداریم.

بهینه‌سازی واقعی از انتخاب یک ترفند شروع نمی‌شود. باید بدانیم کدام متن برای اولین نمایش حیاتی است، چه کاراکترها و وزن‌هایی واقعاً مصرف می‌شوند، فونت جایگزین چقدر با فونت اصلی هم‌اندازه است، مرورگر فایل را چه زمانی کشف می‌کند و نتیجه در اینترنت و دستگاه کاربران ایرانی چیست. این راهنما همین مسیر را از Inventory تا WOFF2، Subset فارسی، font-display، Preload، CORS، Cache، CLS، RUM و Rollback به یک برنامه قابل آزمون تبدیل می‌کند.

خلاصه تصمیم: فونت سریع با «فایل کمتر و قرارداد روشن» ساخته می‌شود

پرسشپاسخ کوتاهشاهد لازم
فونت سفارشی لازم است؟فقط اگر خوانایی، زبان یا هویت برند سود قابل‌اثبات داردتست خوانایی، Brand requirement و مقایسه System font
چه فرمتی؟برای مرورگرهای مدرن معمولاً WOFF2Support matrix واقعی پروژه
Static یا Variable؟براساس تعداد وزن/محور مصرفی، نه نام فناوریحجم Transfer و تعداد Request در Route واقعی
font-display کدام است؟براساس اهمیت نمایش فوری، الزام برند و ریسک SwapFCP/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 آمده است.

  1. HTML و Style sheet کشف می‌شوند.
  2. قواعد @font-face و عناصر دارای font-family تطبیق می‌یابند.
  3. مرورگر برای کاراکتر و Face موردنیاز یک منبع را انتخاب می‌کند.
  4. در دوره انتظار، رفتار نمایش با font-display و سیاست مرورگر تعیین می‌شود.
  5. با رسیدن فونت، متن 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/FaceVazirmatn 400 normalدر کدام Element و Route استفاده می‌شود؟
SourceSame-origin، CDN یا Providerچند اتصال و Trust boundary دارد؟
Transfer/Decodedمثلاً 38KB/91KBهزینه واقعی شبکه و حافظه چیست؟
Discovery timeاز HTML، CSS، JS یا Preloadچرا درخواست دیر آغاز شده است؟
UseBody، Heading، Icon، فقط ModalCritical است یا Lazy/حذف‌شدنی؟
Glyph/AxisPersian+Latin، ۴۰۰/۷۰۰Subset یا Variable چه چیزی را کم می‌کند؟
Display/Metricswap + fallback mismatchFOIT/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 subsetTransfer مجموع و Render cost
Design system با ۱۰۰–۹۰۰VariableAxis range واقعی و Browser matrix
یک Landing با یک وزنStaticRoute-specific bytes
Animation روی AxisVariable با احتیاط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 بالا
fallbackBlock کوتاه و 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 موفق

  1. URL، Format و Credentials mode دقیقاً با درخواست CSS یکسان باشد.
  2. فایل در بخش اولیه همان Route با احتمال بسیار بالا مصرف شود.
  3. Waterfall نشان دهد Discovery فعلی دیر است و اصلاح CSS کافی نیست.
  4. آزمایش ثابت کند 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-hostThird-party
Connectionممکن است اتصال موجود Reuse شودDNS/TCP/TLS یا QUIC جدا
کنترل Subset/Versionبالا؛ مسئولیت با تیموابسته به API/Provider
Cache/CDNکیفیت زیرساخت خودتانکیفیت و Coverage ارائه‌دهنده
Privacy/PolicyTrust 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 versionedHash یا نسخه در نام فایلجایگزینی فایل زیر URL قدیمی
Cache-ControlLong-lived برای Asset immutableTTL کوتاه یا no-cache ناخواسته
CORSOrigin policy محدود و سازگارWildcard/credential mismatch یا Header غایب
Content-Typeنوع درست WOFF2Generic binary و رفتار ناسازگار
Compressionبدون Processing تکراری بی‌فایدهفرض اینکه gzip فایل مشکل Glyph را حل می‌کند
CDN purgeVersion جدید و 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 قدیمی و WebViewFallback متفاوت یا 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 همگانی بسازد.

  1. در HTML و CSS برای fonts.، @font-face و پسوندهای .woff2/.woff جست‌وجو کنید.
  2. Initiator هر Request را به Theme/Plugin/Block نسبت دهید.
  3. یک Owner برای Font manifest تعیین کنید و تعریف تکراری را حذف کنید.
  4. Cache plugin را پس از تغییر Purge و HTML عمومی را با Login خارج بررسی کنید.
  5. Editor، WooCommerce cart/checkout، Popup و زبان دوم را جدا تست کنید.

Minify یا Combine کور می‌تواند ترتیب CSS و URL را تغییر دهد. قبل و بعد از هر Optimization، Network، Computed font و صفحه عمومی را ثبت کنید. چک فنی گسترده‌تر در چک‌لیست سئو فنی از Staging تا انتشار آمده است.

اندازه‌گیری Lab: Waterfall، Filmstrip و CPU را کنار هم ببینید

ابزار/نماپاسخ می‌دهدمحدودیت
Network waterfallDiscovery، Priority، Connection، Transfer و Cacheنمی‌گوید کاربر Shift را چگونه تجربه کرد
Performance/FilmstripFOIT/FOUT، Paint و Layout shiftیک دستگاه و اجرای مصنوعی
Rendering/Layout regionsعنصر جابه‌جا و زمان آنعلت Metric mismatch را باید تحلیل کرد
CoverageCSS/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 حیاتی از NavigationFailure/Timeout rate
Font bytesTransfer مجموع فونت در RouteCache-hit و Repeat view
Font requestsتعداد Faceهای Fetch‌شدهDuplicate/preload waste
LCP/FCPRender milestone واقعیتصویر و JS به‌عنوان Confounder
CLS attributionShiftهای مرتبط با Text containerSession کامل و Post-load
OutcomeTask completion، Read depth یا ConversionError، 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 setupConnection/TTFB/Transfer مقایسه‌ای
Preload هشدار unusedFace در Fold مصرف نیست یا Unicode mismatchCoverage و 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، DesktopDecode/Render و Fallback فرق دارد
BrowserChromium، Firefox، Safari/WebView در SupportDisplay و Metric support متفاوت است
CacheCold، Warm، بازگشت بعد از DeployFirst/Repeat experience متفاوت است
متنفارسی، Mixed، عدد/قیمت، نیم‌فاصله، اعرابSubset و Bidi را آشکار می‌کند
StateHome، Article، Product، Checkout، ErrorFaceهای Lazy و Pluginها متفاوت‌اند

VPN را اگر بخش قابل‌توجهی از کاربران دارد به‌عنوان Segment ثبت کنید، نه اینکه نتیجه آن را به کل ایران تعمیم دهید. Provider eligibility، پرداخت، SLA و امکان دریافت فایل در شرایط اختلال را با تاریخ Snapshot مستند کنید.

مهاجرت کم‌ریسک: از Shadow test تا Rollback

  1. Baseline: Manifest، Waterfall، CWV/RUM، Screenshot و Font corpus فعلی را ذخیره کنید.
  2. Build: WOFF2/Subset/Variable candidate را با License و Hash تولید کنید.
  3. Visual QA: Glyph، Metric، Weight، RTL، Form و Checkout را در Matrix تست کنید.
  4. Shadow: فایل را بدون اعمال عمومی از مسیر CDN/Origin واقعی Fetch و Headerها را بررسی کنید.
  5. Canary: یک Route یا Cohort کوچک با Version روشن منتشر کنید.
  6. Observe: Font ready/bytes/failure، LCP، CLS، Error و Outcome را مقایسه کنید.
  7. Expand: فقط با Guardrail سالم؛ سپس Face قدیمی را پس از دوره امن حذف کنید.
  8. Rollback: CSS manifest قبلی و فایل Versioned باید فوراً قابل بازگشت باشد.

فایل جدید را روی URL قدیمی Overwrite نکنید؛ Cacheهای مختلف ممکن است CSS جدید را با Font قدیمی ترکیب کنند. Versioning محتوامحور و انتشار ترتیب‌دار، Reconciliation را ساده می‌کند.

برنامه ۳۰روزه بهینه‌سازی فونت

بازهخروجیشرط عبور
روز ۱–۵Inventory، Baseline، Route/Device matrix و Ownerتمام Faceها به Initiator و مصرف نسبت داده شده‌اند
روز ۶–۱۰حذف Duplicate/Unused، WOFF2 و Candidateهای Subset/VariableCorpus فارسی و License پاس شده است
روز ۱۱–۱۵Display policy، Fallback metric و Discovery fixFOIT/CLS در Lab کنترل شده است
روز ۱۶–۲۰Preload/CORS/Cache/CDN و WordPress integrationدانلود تکراری و Header failure صفر است
روز ۲۱–۲۵Canary، RUM dashboard و Accessibility QAGuardrailهای CWV/Task/Error سالم‌اند
روز ۲۶–۳۰Rollout، Runbook، Budget و Removal planRollback 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. با این ترتیب، کاربر ایرانی متن را زودتر و پایدارتر می‌بیند، بدون اینکه خوانایی فارسی، دسترس‌پذیری یا امکان بازگشت را قربانی یک امتیاز مصنوعی کنیم.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *