{"id":15698,"date":"2026-09-06T00:40:13","date_gmt":"2026-09-05T22:40:13","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/web-font-loading-privacy-auditor\/"},"modified":"2026-09-06T00:40:13","modified_gmt":"2026-09-05T22:40:13","slug":"web-font-loading-privacy-auditor","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/","title":{"rendered":"Web Font Loading &#038; Privacy Auditor: font-display, woff2, preload, third-party origins"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">A web font decides two things a visitor notices: whether text is visible while the file is still loading, and to whom the browser connects before anyone has agreed to anything. Both are settled in the @font-face rule and in the preload line above it. This check reads the page and its stylesheets, collects every rule and asks each font file for its headers alone.<\/p>\n<div style=\"margin-bottom: 14px;\">\n        <label for=\"schrift-url\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Page address<\/label><br \/>\n        <input id=\"schrift-url\" type=\"text\" value=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\">\n    <\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin-bottom: 16px;\">From this server: one request for the page, up to four for stylesheets, and one HEAD per font file. The font files themselves are never downloaded. No cookies are sent, no scripts are executed, nothing is stored beyond the rate counter.<\/p>\n<p>    <button id=\"schrift-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Check web fonts<\/button><\/p>\n<div id=\"schrift-ausgabe\" style=\"margin-top: 22px;\"><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.8rem; margin: 24px 0 0;\">Limits worth knowing: only stylesheets linked in the served HTML are followed, at most four of them and 512 KB each. Rules that a script adds later stay invisible, and a font declared in CSS but never applied to any element is still counted here.<\/p>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"A web font decides two things a visitor notices: whether text is visible while the file is still loading, and to whom the browser connects before anyone has agreed to anything. Both are settled in the @font-face rule and in the preload line above it. This check reads the page and its stylesheets, collects every rule and asks each font file for its headers alone.\",\"label_url\":\"Page address\",\"datenschutz\":\"From this server: one request for the page, up to four for stylesheets, and one HEAD per font file. The font files themselves are never downloaded. No cookies are sent, no scripts are executed, nothing is stored beyond the rate counter.\",\"knopf\":\"Check web fonts\",\"laeuft\":\"Fetching page and stylesheets...\",\"grenze\":\"Limits worth knowing: only stylesheets linked in the served HTML are followed, at most four of them and 512 KB each. Rules that a script adds later stay invisible, and a font declared in CSS but never applied to any element is still counted here.\",\"h_schriften\":\"@font-face rules found\",\"tabelle_hinweis\":\"Origin says whether the file comes from the same host as the page. Subset means the rule carries a unicode-range, so the browser only loads the characters it needs.\",\"h_befunde\":\"Findings\",\"sp_familie\":\"Family\",\"sp_gewicht\":\"Weight\",\"sp_display\":\"font-display\",\"sp_format\":\"Formats\",\"sp_herkunft\":\"Origin\",\"sp_bereich\":\"Subset\",\"sp_groesse\":\"Size\",\"zeile_seite\":\"Page\",\"zeile_blaetter\":\"Stylesheets read\",\"zeile_faces\":\"@font-face rules\",\"zeile_dauer\":\"Took\",\"zeile_limit\":\"Checks left this hour\",\"wert_fehlt\":\"missing\",\"wert_eigen\":\"same host\",\"wert_ja\":\"yes\",\"wert_nein\":\"no\",\"stufe_hoch\":\"High\",\"stufe_mittel\":\"Medium\",\"stufe_niedrig\":\"Low\",\"stufe_info\":\"Information\",\"stufe_gut\":\"In order\",\"keine_befunde\":\"Nothing to report.\",\"urteil_gut\":\"The fonts load soundly.\",\"urteil_warnung\":\"The fonts work, but text visibility or privacy is being given away.\",\"urteil_kritisch\":\"A font file is unreachable, or a preload makes the browser fetch one twice.\",\"fehler_kein_token\":\"The check could not be started because reCAPTCHA did not load.\",\"fehler_captcha\":\"reCAPTCHA classified the request as automated. Reloading the page normally helps.\",\"fehler_zu_viele\":\"The limit of {limit} checks per hour for this address has been reached.\",\"fehler_adresse\":\"That address cannot be used.\",\"fehler_abruf\":\"The page could not be fetched.\",\"fehler_zaehler\":\"The rate counter is unavailable, so nothing was checked.\",\"fehler_pruefdienst\":\"The reCAPTCHA service could not be reached.\",\"fehler_aufbau\":\"The service is not configured correctly. The fault is on this side.\",\"fehler_eingabe\":\"The request could not be read.\",\"fehler_methode\":\"Wrong request method.\",\"fehler_netz\":\"The service could not be reached.\",\"fehler_antwort\":\"The answer could not be read.\",\"fehler_unbekannt\":\"Something went wrong that has no message of its own.\",\"grund_leer\":\"Nothing was entered.\",\"grund_zu_lang\":\"The input is too long.\",\"grund_unlesbar\":\"It does not parse as an address.\",\"grund_schema\":\"Only http and https addresses are checked.\",\"grund_benutzerinfo\":\"Inputs with credentials in them are refused.\",\"grund_kein_punkt\":\"The host name needs at least one dot.\",\"grund_hostform\":\"A host name is expected, not an IP address.\",\"grund_port\":\"Only the ports 80, 443, 8080 and 8443 are checked.\",\"grund_gesperrter_bereich\":\"That host resolves into a private or reserved network. Those are never fetched.\",\"grund_kein_dns\":\"The host name does not resolve.\",\"grund_abruf\":\"The address did not answer.\",\"grund_zu_viele_spruenge\":\"The address redirects too often.\",\"befund_status\":\"The page answered with status {status} || Stylesheets were still read from whatever body came back, but a page that does not answer with 200 rarely shows what visitors see.\",\"befund_blatt_unerreichbar\":\"A stylesheet could not be fetched || {url}, reason: {grund}. Font rules inside it stay unknown, so this check may be incomplete.\",\"befund_blatt_status\":\"A stylesheet answered with status {status} || {url}. Whatever font rules it holds are missing from this report, and from the page.\",\"befund_blatt_gekappt\":\"A stylesheet was longer than {kb} KB || {url}. It was read up to that point; rules beyond it were not seen.\",\"befund_nur_stichprobe_css\":\"{gesamt} stylesheets are linked, the first {n} were read || A sample keeps the load on the checked server small. Rules in the remaining files are not part of this report.\",\"befund_keine_schriften\":\"No @font-face rule found || The page either uses fonts installed on the visitor\\u0027s device or loads none at all. That is the fastest and the most private option there is: zero bytes, zero third-party connections.\",\"befund_schrift_unerreichbar\":\"The font file for {familie} could not be fetched || {url}, reason: {grund}. Browsers fall back to the next family in the list, so the page looks different from what the design assumes.\",\"befund_schrift_status\":\"The font file for {familie} answered with status {status} || {url}. The rule points at a file the server does not deliver.\",\"befund_schrift_ohne_cache_control\":\"The font file for {familie} carries no Cache-Control || {url}. Fonts change about as often as never; without the header every cache decides for itself, and some ask again on the next visit.\",\"befund_schrift_kurzer_cache\":\"The font file for {familie} may be cached for {sekunden} seconds || For a file whose content never changes that is short. A year is the usual value once the address carries a version.\",\"befund_ohne_font_display\":\"{n} of {gesamt} rules carry no font-display || The default is auto, and every widespread browser treats it like block: text in that family stays invisible for up to three seconds while the file loads. swap shows the fallback immediately and exchanges it later.\",\"befund_font_display_block\":\"{n} rules use font-display: auto or block || Both hide the text until the font arrives. On a slow connection that is a page which shows headings and no words.\",\"befund_font_display_gut\":\"All {n} rules set a font-display that keeps text visible || swap, fallback or optional \u2014 text appears in the fallback family and is exchanged when the file arrives.\",\"befund_ohne_unicode_range\":\"{n} rules carry no unicode-range || Without it the browser loads the whole character set, including the alphabets the page never uses. Subsetting is what makes a font file small in the first place.\",\"befund_ohne_woff2\":\"{n} rules offer no woff2 || woff2 is the only format worth serving today: it is understood everywhere that matters and is roughly a third smaller than woff.\",\"befund_schwere_schriften\":\"{n} font files weigh more than {grenze_kb} KB || A single weight of a subsetted Latin font is typically 15 to 30 KB. Anything far above that carries either many alphabets or many glyphs nobody reads.\",\"befund_viele_familien\":\"{n} different families are declared || Every family costs at least one file, and usually several weights. Two families are a design decision; five are usually a leftover.\",\"befund_schriften_gefunden\":\"{faces} rules across {familien} families || That is what the stylesheets declare, not necessarily what the page uses.\",\"befund_schriften_von_google\":\"Fonts are loaded from Google || {hosts}. The browser connects to Google before anything is agreed, and the visitor\\u0027s IP address travels with the request. Under the GDPR that has been the subject of court decisions in Germany; hosting the files locally removes the question entirely and usually loads faster.\",\"befund_schriften_fremd\":\"Fonts come from {n} third-party host(s) || {hosts}. Every third-party host means one more DNS lookup, one more connection and one more party that sees the visitor\\u0027s IP address.\",\"befund_fremder_host_ohne_preconnect\":\"No preconnect for {host} || The connection to a third-party font host is only opened once the stylesheet has been parsed. A preconnect in the head starts it earlier and typically saves a hundred milliseconds or more.\",\"befund_schriften_eigen\":\"All fonts come from the page\\u0027s own host || No third party is involved, and the connection is already open when the request goes out.\",\"befund_preload_ohne_crossorigin\":\"A font is preloaded without crossorigin || {url}. Fonts are always fetched anonymously, so the preloaded entry does not match the later request: the browser loads the file a second time. The attribute costs nothing and is required even for a font on the page\\u0027s own host.\",\"befund_preload_gut\":\"A font is preloaded with crossorigin || {url}. That is the combination that actually saves the request instead of doubling it.\",\"befund_kein_preload\":\"No font is preloaded || Fonts are discovered only after the stylesheet has been parsed. A preload for the one font above the fold moves that discovery forward; more than one or two rarely helps.\"};<\/p>\n<p>    var ENDPUNKT = '\/lw-schriftcheck.php';\n    var SITEKEY = '6LcrPkEtAAAAAPo1QCOf-IIM2fCL0UfdJz4y2iSY';\n    var STUFEN = ['hoch', 'mittel', 'niedrig', 'info', 'gut'];<\/p>\n<p>    function liste(w) { return Array.isArray(w) ? w : []; }\n    function zahl(w) { return (typeof w === 'number' && isFinite(w)) ? w : null; }\n    function zeichen(w) { return (typeof w === 'string' && w !== '') ? w : null; }\n    function text(schluessel, daten) {\n        var t = T[schluessel];\n        if (typeof t !== 'string') { return null; }\n        return t.replace(\/\\{([a-z_0-9]+)\\}\/g, function (m, k) {\n            return (daten && daten[k] !== undefined && daten[k] !== null) ? String(daten[k]) : m;\n        });\n    }\n    function kurz(url) {\n        var s = String(url || '').replace(\/^https?:\\\/\\\/\/, '');\n        return s.length <= 52 ? s : s.slice(0, 24) + '\u2026' + s.slice(-26);\n    }\n\n    function bewerten(antwort) {\n        var a = antwort || {};\n        if (a.fehler) { return { fehler: String(a.fehler), grund: a.grund || null, limit: zahl(a.limit) }; }\n        var befunde = liste(a.befunde).map(function (f) {\n            return { key: String(f.key || ''), stufe: STUFEN.indexOf(f.stufe) === -1 ? 'info' : f.stufe, daten: (f.daten &#038;&#038; typeof f.daten === 'object') ? f.daten : {} };\n        });\n        var gruppen = {};\n        STUFEN.forEach(function (s) { gruppen[s] = befunde.filter(function (f) { return f.stufe === s; }); });\n        var schriften = liste(a.schriften).map(function (z) {\n            return {\n                familie: zeichen(z.familie), gewicht: zeichen(z.gewicht), display: zeichen(z.display),\n                formate: liste(z.formate).filter(function (x) { return typeof x === 'string'; }),\n                host: zeichen(z.host), fremd: z.fremd === true, bereich: z.bereich === true,\n                kb: zahl(z.kb), status: zahl(z.status)\n            };\n        });\n        return {\n            fehler: null, url: String(a.url || ''), endurl: String(a.endurl || ''), host: String(a.host || ''),\n            urteil: (a.urteil === 'gut' || a.urteil === 'warnung' || a.urteil === 'kritisch') ? a.urteil : 'kritisch',\n            blaetter: zahl(a.blaetter), schriften: schriften,\n            befunde: befunde, gruppen: gruppen, dauer: zahl(a.dauer_ms), limitRest: zahl(a.limit_rest)\n        };\n    }\n\n    function skriptLaden() {\n        return new Promise(function (auf) {\n            if (window.grecaptcha) { auf(true); return; }\n            var s = document.createElement('script');\n            s.src = 'https:\/\/www.google.com\/recaptcha\/api.js?render=' + SITEKEY;\n            s.onload = function () { auf(true); }; s.onerror = function () { auf(false); };\n            document.head.appendChild(s);\n            setTimeout(function () { auf(!!window.grecaptcha); }, 8000);\n        });\n    }\n    function tokenHolen() {\n        return skriptLaden().then(function (da) {\n            if (!da || !window.grecaptcha || !window.grecaptcha.ready) { return null; }\n            return new Promise(function (auf) {\n                var fertig = false;\n                function einmal(w) { if (!fertig) { fertig = true; auf(w); } }\n                setTimeout(function () { einmal(null); }, 12000);\n                try {\n                    window.grecaptcha.ready(function () {\n                        if (fertig) { return; }\n                        if (!window.grecaptcha.execute) { einmal(null); return; }\n                        window.grecaptcha.execute(SITEKEY, { action: 'schriftcheck' }).then(function (t) { einmal(t || null); }, function () { einmal(null); });\n                    });\n                } catch (e) { einmal(null); }\n            });\n        }, function () { return null; });\n    }\n    function abfragen(url) {\n        return tokenHolen().then(function (token) {\n            return fetch(ENDPUNKT, { method: 'POST', headers: { 'Content-Type': 'application\/json' }, body: JSON.stringify({ url: url, token: token || '' }) })\n                .then(function (r) { return r.json().then(function (j) { return j; }, function () { return { fehler: 'antwort' }; }); }, function () { return { fehler: 'netz' }; });\n        });\n    }\n\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.SCHRIFT = { bewerten: bewerten, darstellen: null, text: text, kurz: kurz, ENDPUNKT: ENDPUNKT, STUFEN: STUFEN, T: T };\n\n    var btn = document.getElementById('schrift-btn');\n    var out = document.getElementById('schrift-ausgabe');\n    if (!btn || !out) { return; }\n\n    function el(tag, stil, txt) { var e = document.createElement(tag); if (stil) { e.setAttribute('style', stil); } if (txt !== undefined &#038;&#038; txt !== null) { e.textContent = txt; } return e; }\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.95rem; margin: 22px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var ZELLE_WERT = 'padding: 5px 12px 5px 0; color: var(--text-primary, #e8e8ee); font-size: 0.87rem; font-family: monospace; word-break: break-all;';\n    var KASTEN = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 8px; padding: 14px 16px; margin: 0 0 10px;';\n    var GUT = ' color: #7ee787;', WARN = ' color: #ffa94d;', ROT = ' color: #ff7b72;', GRAU = ' color: #8a8a99;';\n    var FARBEN = { hoch: '#ff7b72', mittel: '#ffa94d', niedrig: '#e3b341', info: '#8a8a99', gut: '#7ee787' };\n    var URTEIL = { gut: GUT, warnung: WARN, kritisch: ROT };\n    function gitter(sp) { return el('div', 'display: grid; grid-template-columns: ' + sp + '; gap: 0 18px; align-items: baseline;'); }\n    function paar(g, n, w, stil) { g.appendChild(el('div', ZELLE, n)); g.appendChild(el('div', ZELLE_WERT + (stil || ''), w)); }\n\n    function tabelle(b) {\n        var huelle = el('div', 'overflow-x: auto; margin: 0 0 6px;');\n        var t = el('table', 'border-collapse: collapse; width: 100%; min-width: 640px; font-size: 0.84rem;');\n        var kopf = el('tr', '');\n        [T.sp_familie, T.sp_gewicht, T.sp_display, T.sp_format, T.sp_herkunft, T.sp_bereich, T.sp_groesse].forEach(function (n) {\n            kopf.appendChild(el('th', 'text-align: left; padding: 6px 12px 6px 0; color: #8a8a99; font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.4px; border-bottom: 1px solid var(--border, #2a2a35); white-space: nowrap;', n));\n        });\n        t.appendChild(kopf);\n        b.schriften.forEach(function (z) {\n            var r = el('tr', '');\n            function zelle(txt, stil) { r.appendChild(el('td', 'padding: 7px 12px 7px 0; border-bottom: 1px solid var(--border, #2a2a35); font-family: monospace; color: var(--text-primary, #e8e8ee);' + (stil || ''), txt)); }\n            zelle(z.familie || '-');\n            zelle(z.gewicht || '-', GRAU);\n            var gutesDisplay = z.display === 'swap' || z.display === 'fallback' || z.display === 'optional';\n            zelle(z.display || T.wert_fehlt, gutesDisplay ? GUT : WARN);\n            zelle(z.formate.length ? z.formate.join(', ') : '-', z.formate.indexOf('woff2') !== -1 ? GUT : WARN);\n            zelle(z.fremd ? (z.host || '-') : T.wert_eigen, z.fremd ? WARN : GUT);\n            zelle(z.bereich ? T.wert_ja : T.wert_nein, z.bereich ? GUT : GRAU);\n            zelle(z.kb !== null ? z.kb + ' KB' : '-', GRAU);\n            t.appendChild(r);\n        });\n        huelle.appendChild(t);\n        return huelle;\n    }\n\n    function darstellen(b, ziel) {\n        ziel.innerHTML = '';\n        if (b.fehler) {\n            var txt = T['fehler_' + b.fehler] || T.fehler_unbekannt;\n            if (b.grund &#038;&#038; T['grund_' + b.grund]) { txt = txt + ' ' + T['grund_' + b.grund]; }\n            if (b.limit !== null) { txt = txt.replace('{limit}', String(b.limit)); }\n            ziel.appendChild(el('p', ZELLE + WARN + ' margin: 0;', txt));\n            return;\n        }\n        ziel.appendChild(el('div', 'font-size: 1.05rem; font-weight: 700; margin: 0 0 12px;' + URTEIL[b.urteil], T['urteil_' + b.urteil]));\n\n        var g = gitter('auto auto');\n        paar(g, T.zeile_seite, b.endurl || b.url, GRAU);\n        paar(g, T.zeile_blaetter, String(b.blaetter !== null ? b.blaetter : '-'), GRAU);\n        paar(g, T.zeile_faces, String(b.schriften.length), GRAU);\n        if (b.dauer !== null) { paar(g, T.zeile_dauer, b.dauer + ' ms', GRAU); }\n        if (b.limitRest !== null) { paar(g, T.zeile_limit, String(b.limitRest), GRAU); }\n        ziel.appendChild(g);\n\n        if (b.schriften.length) {\n            ziel.appendChild(el('div', UEBERSCHRIFT, T.h_schriften));\n            ziel.appendChild(tabelle(b));\n            ziel.appendChild(el('p', ZELLE + ' margin: 0 0 6px; font-size: 0.8rem;', T.tabelle_hinweis));\n        }\n\n        ziel.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n        var irgendwas = false;\n        STUFEN.forEach(function (s) {\n            var gr = b.gruppen[s];\n            if (!gr.length) { return; }\n            irgendwas = true;\n            ziel.appendChild(el('div', 'font-weight: 700; font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.5px; margin: 14px 0 6px; color: ' + FARBEN[s] + ';', T['stufe_' + s] + ' (' + gr.length + ')'));\n            gr.forEach(function (f) {\n                var k = el('div', KASTEN + ' border-left: 3px solid ' + FARBEN[s] + ';');\n                var daten = {};\n                Object.keys(f.daten).forEach(function (n) { daten[n] = (n === 'url') ? kurz(f.daten[n]) : f.daten[n]; });\n                var t = text('befund_' + f.key, daten) || f.key;\n                var teile = t.split(' || ');\n                k.appendChild(el('div', 'color: var(--text-primary, #e8e8ee); font-size: 0.9rem; font-weight: 700;', teile[0]));\n                if (teile[1]) { k.appendChild(el('div', ZELLE + ' padding: 6px 0 0; line-height: 1.55;', teile[1])); }\n                ziel.appendChild(k);\n            });\n        });\n        if (!irgendwas) { ziel.appendChild(el('p', ZELLE + ' margin: 0;', T.keine_befunde)); }\n    }\n    window.LW_TEST.SCHRIFT.darstellen = darstellen;\n\n    var laeuft = false;\n    btn.addEventListener('click', function () {\n        if (laeuft) { return; }\n        laeuft = true; btn.disabled = true;\n        out.innerHTML = '';\n        out.appendChild(el('p', ZELLE + GRAU + ' margin: 0;', T.laeuft));\n        abfragen(wert('schrift-url')).then(function (a) { darstellen(bewerten(a), out); laeuft = false; btn.disabled = false; },\n            function () { darstellen(bewerten({ fehler: 'netz' }), out); laeuft = false; btn.disabled = false; });\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fetches a page and its stylesheets, reads every @font-face rule and checks what actually costs time or privacy: whether font-display is set at all, whether the file comes from a third-party host, whether woff2 is offered, whether subsetting is used, and whether a preload for a font carries crossorigin. Font files are never downloaded \u2014 only their headers are asked for.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[91279,92757,91315],"class_list":["post-15698","page","type-page","status-publish","hentry","tag-prywatnosc-danych-pl","tag-web-development","tag-web-performance"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Web Font Loading &amp; Privacy Auditor: font-display, woff2, preload, third-party origins - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Web Font Loading &amp; Privacy Auditor: font-display, woff2, preload, third-party origins - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Fetches a page and its stylesheets, reads every @font-face rule and checks what actually costs time or privacy: whether font-display is set at all, whether the file comes from a third-party host, whether woff2 is offered, whether subsetting is used, and whether a preload for a font carries crossorigin. Font files are never downloaded \u2014 only their headers are asked for.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/web-font-loading-privacy-auditor\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/web-font-loading-privacy-auditor\\\/\",\"name\":\"Web Font Loading & Privacy Auditor: font-display, woff2, preload, third-party origins - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-09-05T22:40:13+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/web-font-loading-privacy-auditor\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/web-font-loading-privacy-auditor\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/web-font-loading-privacy-auditor\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Web Font Loading &#038; Privacy Auditor: font-display, woff2, preload, third-party origins\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Web Font Loading & Privacy Auditor: font-display, woff2, preload, third-party origins - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/","og_locale":"en_US","og_type":"article","og_title":"Web Font Loading & Privacy Auditor: font-display, woff2, preload, third-party origins - Lukas Wojcik - Blog","og_description":"Fetches a page and its stylesheets, reads every @font-face rule and checks what actually costs time or privacy: whether font-display is set at all, whether the file comes from a third-party host, whether woff2 is offered, whether subsetting is used, and whether a preload for a font carries crossorigin. Font files are never downloaded \u2014 only their headers are asked for.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/","name":"Web Font Loading & Privacy Auditor: font-display, woff2, preload, third-party origins - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-09-05T22:40:13+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/web-font-loading-privacy-auditor\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"Web Font Loading &#038; Privacy Auditor: font-display, woff2, preload, third-party origins"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/15698","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=15698"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/15698\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=15698"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=15698"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}