Βελτιστοποίηση Γραμματοσειρών Web: Font-Display, Subsetting & Variable Fonts για Ταχύτερες Ιστοσελίδες (2026)

Πλήρης οδηγός βελτιστοποίησης web fonts για 2026: WOFF2, font-display, subsetting, variable fonts, preload, self-hosting και fallback metric overrides για μηδενικό CLS. Με πραγματικό κώδικα από production.

Βελτιστοποίηση Web Fonts: Οδηγός 2026

Ενημερώθηκε: 2 Αυγούστου 2026

Η βελτιστοποίηση γραμματοσειρών web είναι το σύνολο τεχνικών που μειώνουν το μέγεθος, την καθυστέρηση φόρτωσης και την οπτική αστάθεια που προκαλούν οι custom fonts. Συνδυάζει WOFF2, font-display: swap, subsetting, variable fonts και σωστό preload. Στα projects που ανέλαβα φέτος, μια σοβαρή στρατηγική fonts κόβει συνήθως 300–800ms από το LCP και ρίχνει το CLS από 0.15+ σε <0.05. Ο παρακάτω οδηγός καλύπτει όλη τη σύγχρονη ροή για το 2026 με πραγματικό κώδικα από production.

  • Χρησιμοποίησε WOFF2 αποκλειστικά. Η υποστήριξη είναι 98%+ και συμπιέζει 30% καλύτερα από WOFF.
  • Το font-display: swap είναι η ασφαλής προεπιλογή για κείμενο, αλλά το optional είναι καλύτερο για LCP όταν αποδέχεσαι fallback στην πρώτη επίσκεψη.
  • Το subsetting με unicode-range ή pyftsubset μπορεί να μειώσει ένα αρχείο 200KB σε 20–40KB για ελληνικά συν λατινικά.
  • Οι Variable fonts ενοποιούν weights και styles σε ένα αρχείο, συχνά μικρότερο συνολικά από 2–3 static weights.
  • Το self-hosting των Google Fonts κερδίζει τουλάχιστον έναν TCP handshake κι ένα DNS lookup.
  • Χρησιμοποίησε size-adjust, ascent-override και descent-override στα fallback fonts για να εξαλείψεις σχεδόν όλο το font-related CLS.

Γιατί μόνο WOFF2 το 2026

Ειλικρινά, στα audits που τρέχω κάθε εβδομάδα σε ecommerce sites Series B/C βλέπω ακόμη sites να σερβίρουν .ttf ή .otf direct, και συχνά τρία fallback formats στο ίδιο @font-face. Αυτό είναι σκέτο bloat. Το WOFF2 έχει 98.5% global support σύμφωνα με τον Can I Use από τις αρχές 2025, και συμπιέζει περίπου 30% καλύτερα από το WOFF1 χάρη στον Brotli-based αλγόριθμο. Δεν χρειάζεσαι EOT (IE-only), δεν χρειάζεσαι SVG fonts (deprecated από Chrome/Safari), και δεν χρειάζεσαι raw TTF/OTF στο web.

Ένα καθαρό @font-face block πρέπει να δείχνει ακριβώς έτσι:

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700; /* variable range */
  font-display: swap;
  src: url('/fonts/inter-var-latin-greek.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0370-03FF; /* latin + greek */
}

Αν το src σου περιέχει fallback σε WOFF ή TTF, διαγράψέ τα. Ο browser που δεν υποστηρίζει WOFF2 στο 2026 (κυρίως πολύ παλιοί embedded browsers) θα δείξει το system fallback και είναι OK. Επίσης μην ξεχάσεις να στείλεις Content-Type: font/woff2 και Cache-Control: public, max-age=31536000, immutable από τον CDN. Τα fonts δεν αλλάζουν, είναι fingerprinted στο URL.

Τι κάνει το font-display και πότε να χρησιμοποιήσεις κάθε τιμή

Το font-display descriptor του MDN ελέγχει τι θα κάνει ο browser κατά τη διάρκεια που κατεβαίνει η γραμματοσειρά. Οι τιμές (auto, block, swap, fallback, optional) καθορίζουν δύο περιόδους: το block period (invisible text) και το swap period (fallback shown, ανταλλαγή όταν φτάσει).

ΤιμήBlock periodSwap periodΚαλύτερο για
block~3sάπειροΛογότυπα, icon fonts
swap0msάπειροBody text, ασφαλής default
fallback~100ms~3sSub-headings που πρέπει να δείχνουν σωστά
optional~100ms0msBody text με προτεραιότητα σε LCP/CLS
autoUA default (~block)UA defaultΠοτέ (αποφύγετέ το)

Η κοινή σοφία λέει «πάντα swap», αλλά είναι πιο διαφοροποιημένο από αυτό. Στα ecommerce projects που μετράω με RUM, το optional δίνει το καλύτερο LCP score επειδή, αν το font δεν προλάβει τα ~100ms του block period, γίνεται skip και χρησιμοποιείται το fallback. Αποφεύγεις εντελώς το FOUT στην πρώτη επίσκεψη. Στη δεύτερη επίσκεψη το font είναι στο cache και εμφανίζεται αμέσως.

Πώς να κάνεις preload γραμματοσειρές σωστά

Οι custom fonts ανακαλύπτονται από τον browser μόνο αφού parser-άρει το CSS και βρει έναν κανόνα που τις χρησιμοποιεί. Αυτό σημαίνει ότι σε ένα τυπικό waterfall, η γραμματοσειρά ξεκινά να κατεβαίνει 300–800ms μετά το initial HTML, αργά για ένα Core Web Vital όπως το LCP. Το <link rel="preload"> λύνει αυτό ξεκινώντας το request στο ίδιο round-trip με το HTML.

<link
  rel="preload"
  href="/fonts/inter-var-latin.woff2"
  as="font"
  type="font/woff2"
  crossorigin
  fetchpriority="high"
>

Τρεις παγίδες που βλέπω συνέχεια σε reviews:

  • Ξεχνάς το crossorigin. Τα fonts πάντα κατεβαίνουν με CORS mode, ακόμα και same-origin. Χωρίς αυτό το attribute, ο browser κάνει το request δύο φορές. Απλό σφάλμα, +200ms LCP.
  • Preload σε πάρα πολλά fonts. Preload μόνο ό,τι χρησιμοποιείται πάνω από το fold, συνήθως ένα weight/style. Τα υπόλοιπα ας φορτώσουν κανονικά.
  • Preload σε σελίδες που δεν το χρησιμοποιούν. Αν το font εμφανίζεται μόνο σε συγκεκριμένα templates, μην το βάλεις global στο header.

Αν χρησιμοποιείς third-party host (Google Fonts, Adobe Fonts), βάλε preconnect για να ανοίξεις TCP+TLS νωρίς. Παρόλα αυτά, όπως θα δεις παρακάτω, το self-hosting είναι σχεδόν πάντα η καλύτερη λύση. Για συνδυασμό του preload με άλλες optimizations δες τον οδηγό μας για τη βελτιστοποίηση LCP για το 2026.

Πρέπει να κάνω self-host τα Google Fonts;

Ναι, σχεδόν πάντα. Από το 2020 και μετά που ο Chrome εγκατέλειψε το partitioned HTTP cache για third-party origins, ο «shared cache» επιχείρημα που δικαιολογούσε το CDN των Google Fonts είναι νεκρός. Κάθε site κατεβάζει το font ξεχωριστά. Επιπλέον έχεις:

  • Ένα extra DNS lookup στο fonts.googleapis.com
  • Ένα TCP handshake συν TLS negotiation
  • Ένα δεύτερο round-trip στο fonts.gstatic.com
  • GDPR/ePrivacy εκθέσεις (το γερμανικό δικαστήριο του Μονάχου έχει ήδη επιβάλει πρόστιμα για embedded Google Fonts)

Ο απλούστερος τρόπος για self-hosting είναι με το Google Webfonts Helper. Επιλέγεις τη γραμματοσειρά, τα charsets που θες (latin + greek για ελληνικό site), κατεβάζεις ένα zip με τα WOFF2 files κι ένα έτοιμο CSS snippet. Βάζεις τα fonts κάτω από /public/fonts/, κάνεις rewrite το CSS να δείχνει στη διαδρομή σου, και σβήνεις οποιοδήποτε <link> προς fonts.googleapis.com.

/* /styles/fonts.css — self-hosted, subsetted, ready for prod */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-latin-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-greek-400.woff2') format('woff2');
  unicode-range: U+0370-03FF, U+1F00-1FFF;
}

Το κέρδος στην πρώτη μου δουλειά για έναν Ελληνικό retailer ήταν -410ms LCP στο p75 mobile απλώς από τη μεταφορά 3 Google Fonts weights στο ίδιο origin, πίσω από τον υπάρχοντα CDN (Cloudflare). Για γενικότερες network wins δες και τη στρατηγική μείωσης TTFB.

Subsetting: μείωση μεγέθους 60–90% με unicode-range

Ένα full Latin+Greek+Cyrillic file της Inter είναι ~200KB. Αν η σελίδα σου χρησιμοποιεί μόνο ελληνικά και βασικά λατινικά χαρακτήρες, μπορείς να το ρίξεις σε ~40KB. Δύο τρόποι:

Runtime subsetting με unicode-range

Ο browser κατεβάζει ένα subset ΜΟΝΟ αν εμφανίζεται τουλάχιστον ένας χαρακτήρας από το range. Αυτό είναι το μαγικό πίσω από τα «autohint» URLs των Google Fonts.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-greek.woff2') format('woff2');
  unicode-range: U+0370-03FF, U+1F00-1FFF;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0400-04FF;
}

Build-time subsetting με pyftsubset

Για μέγιστο έλεγχο (π.χ. αφαίρεση συγκεκριμένων OpenType features) χρησιμοποίησε pyftsubset από το fontTools της Google:

# pip install fonttools brotli zopfli
pyftsubset Inter-Variable.ttf \
  --output-file=inter-var-el.woff2 \
  --flavor=woff2 \
  --with-zopfli \
  --layout-features='kern,liga,calt,locl' \
  --unicodes="U+0000-00FF,U+0370-03FF,U+1F00-1FFF" \
  --no-hinting \
  --desubroutinize

Στα δικά μας builds αυτό ρίχνει την Inter Variable από 340KB σε 62KB για ελληνικό content, μια νίκη 82%. Τρέξε το ως pre-build step σε CI και commit το output, ή δοκίμασε plugin όπως το fontaine για Vite/Nuxt που κάνει subsetting αυτόματα.

Variable fonts: πότε είναι πραγματικά μικρότερα

Ένα variable font πακετάρει πολλαπλά weights, widths και italics σε ένα αρχείο μέσω axes (wght, wdth, ital, slnt, opsz). Στη θεωρία εξοικονομείς requests. Στην πράξη όμως ένα variable file είναι πάντα μεγαλύτερο από ένα single static weight, οπότε το κέρδος εμφανίζεται μόνο όταν χρησιμοποιείς 3 ή περισσότερα weights/styles.

Παράδειγμα με Inter (WOFF2, latin-only, subsetted):

  • Static Regular 400: 18KB
  • Static Regular 400 + Bold 700 + Italic 400 = 54KB
  • Variable (wght 100–900 + italic axis) = 48KB

Άρα αν χρησιμοποιείς μόνο 400 και 700, τα δύο static είναι μικρότερα. Αν έχεις 400+500+600+700 ή animated weight transitions (design systems, dark/light theme με weight change) το variable κερδίζει.

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900; /* variable range */
  src: url('/fonts/inter-var-latin.woff2') format('woff2-variations');
}

h1 { font-weight: 750; } /* interpolated axis value */
strong { font-weight: 620; }
body { font-weight: 400; }

Fallback metric overrides για μηδενικό CLS

Η κύρια πηγή font-related CLS είναι όταν το custom font «swap»-άρει με το system fallback και έχει διαφορετικές διαστάσεις. Τα paragraphs αναδιατάσσονται, το layout πηδάει. Οι CSS descriptors size-adjust, ascent-override, descent-override και line-gap-override λύνουν αυτό μόνιμα, «κουμπώνοντας» ένα local fallback στις μετρικές του custom font.

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107.4%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Inter', 'Inter Fallback', sans-serif;
}

Τις τιμές δεν τις μαντεύεις, τις υπολογίζεις. Δύο δοκιμασμένα εργαλεία:

  1. Fallback Font Generator. Παίρνει το WOFF2 και δίνει έτοιμο CSS block.
  2. @capsizecss/metrics. Node.js API που εξάγει metrics από κάθε font και σου δίνει programmatic έλεγχο για styleguide αυτοματοποίηση.

Στα Next.js 15 projects, το next/font εφαρμόζει αυτόματα ένα fallback size-adjust. Για custom setups όμως πρέπει να το κάνεις χειροκίνητα. Ένα σωστά ρυθμισμένο fallback κόβει το CLS από 0.15+ σε <0.02 σε mobile. Δες τον πλήρη οδηγό βελτιστοποίησης CLS για τη μεγαλύτερη εικόνα.

JavaScript font loading strategies

Για πιο δυναμικά σενάρια (π.χ. lazy-load γραμματοσειρών ανά route, ή load μόνο μετά από user interaction) η CSS Font Loading API είναι η κατάλληλη επιλογή. Πλήρως υποστηριζόμενη σε όλους τους evergreen browsers.

// Lazy-load ένα βαρύ display font μόνο αν το χρειάζεται η route
async function loadHeadlineFont() {
  const font = new FontFace(
    'PP Editorial New',
    'url(/fonts/editorial-new.woff2) format("woff2")',
    { display: 'swap', weight: '400' }
  );

  try {
    await font.load();
    document.fonts.add(font);
    document.documentElement.classList.add('editorial-loaded');
  } catch (err) {
    // αν αποτύχει, τα headlines παραμένουν σε system font, no big deal
  }
}

if (document.querySelector('.hero-editorial')) {
  loadHeadlineFont();
}

Ένα κλασικό pattern για ecommerce είναι να καθυστερείς μη-critical fonts μέχρι το load event ή το requestIdleCallback:

if ('requestIdleCallback' in window) {
  requestIdleCallback(() => loadHeadlineFont(), { timeout: 2000 });
} else {
  window.addEventListener('load', loadHeadlineFont);
}

Ένα σημείο προσοχής: μην χρησιμοποιήσεις JS-driven loading για το font που εμφανίζεται στο LCP element. Προσθέτεις εξάρτηση σε JS parsing και ακυρώνεις το κέρδος. Preload συν CSS @font-face είναι πάντα η καλύτερη διαδρομή για above-the-fold text.

Πώς να ελέγξεις fonts με Lighthouse και WebPageTest

Οι audits που τρέχω σε PR gates με Lighthouse CI (μιλάω αναλυτικά για setup στο επόμενο άρθρο) εστιάζουν σε τρία font-related metrics:

  1. «Ensure text remains visible during webfont load». Αν αποτύχει, έχεις font-display: block ή auto κάπου.
  2. LCP contribution. Αν το LCP element είναι κείμενο, το Lighthouse δείχνει σε ποιο ms το font resolve-άρει.
  3. Total byte weight of fonts. Αν ξεπερνά τα 100KB, έχεις χώρο για subsetting ή αφαίρεση weights.

Στο WebPageTest, το «Filmstrip view» και το «Waterfall» δείχνουν ξεκάθαρα font swaps. Ψάξε για δύο renders του ίδιου κειμένου. Αυτό είναι το FOUT και είναι ο ένοχος για font-CLS. Για production monitoring χρησιμοποίησε το RUM με web-vitals.js ώστε να δεις πραγματικά νούμερα από πραγματικούς χρήστες, όχι synthetic labs.

Συχνές ερωτήσεις

Τι είναι το font-display: swap και πότε πρέπει να το χρησιμοποιώ;

Το font-display: swap λέει στον browser να δείξει αμέσως το fallback font και να το ανταλλάξει με το custom font μόλις αυτό κατέβει. Είναι η ασφαλής προεπιλογή για όλο το body text γιατί εξαλείφει το FOIT (invisible text). Για κρίσιμο LCP text σκέψου και το optional.

Πόσο μεγαλύτερα είναι τα variable fonts από τα static;

Ένα variable font με πλήρες weight axis (100–900) είναι συνήθως 2–3x μεγαλύτερο από ένα single static weight, αλλά μικρότερο από 3+ static weights αθροιστικά. Το break-even σημείο είναι στα 3 styles. Κάτω από αυτό, τα static κερδίζουν στο byte size.

Πρέπει να χρησιμοποιώ preload για όλα τα fonts;

Όχι. Preload μόνο τα fonts που εμφανίζονται πάνω από το fold στην πρώτη οθόνη, συνήθως ένα ή δύο weights. Το να κάνεις preload πολλά fonts μειώνει το bandwidth budget για άλλους critical assets όπως το LCP image ή τα CSS/JS bundles.

Είναι τα Google Fonts πιο αργά από self-hosted;

Ναι, στη σχεδόν συντριπτική πλειοψηφία των περιπτώσεων. Ο third-party origin απαιτεί extra DNS lookup, TCP handshake και TLS negotiation. Από την κατάργηση του shared browser cache το 2020, δεν υπάρχει αντισταθμιστικό όφελος. Το self-hosting κερδίζει 200–600ms σε mobile.

Πώς μειώνω το CLS που προκαλείται από γραμματοσειρές;

Χρησιμοποίησε τα CSS descriptors size-adjust, ascent-override και descent-override σε ένα @font-face για ένα local fallback (π.χ. Arial). Έτσι το layout του fallback ταιριάζει ακριβώς με το custom font και η ανταλλαγή δεν προκαλεί καμία μετατόπιση.

Χρειάζομαι ακόμη WOFF και TTF fallback το 2026;

Όχι. Το WOFF2 έχει 98.5% global browser support. Οι εναλλακτικές formats προσθέτουν πολυπλοκότητα στο src chain χωρίς πραγματικό όφελος. Αφαίρεσέ τες και σέρβιρε μόνο WOFF2 με σωστά headers cache.

Σχετικά με τον Συγγραφέα Priya Ravindran

Priya is a frontend performance engineer with 11 years of experience untangling slow React and Next.js apps. She spent four years at Shopify on the Storefront Performance team, where she drove a project that cut median LCP across the Online Store theme platform from 3.4s to 1.8s by rewriting the critical render path and killing third-party tag bloat. Before that she shipped checkout perf work at Klarna. These days she runs an independent practice helping Series B/C ecommerce companies hit a 75th-percentile INP under 200ms before they bother with redesigns. She has a soft spot for Lighthouse CI in pull-request gates and writes most of her perf budgets in YAML. Outside work she's slowly restoring a 1998 Honda CB400 and arguing with her partner about whether RUM beats lab data (it does).