{"id":1067,"date":"2026-09-01T09:00:00","date_gmt":"2026-09-01T07:00:00","guid":{"rendered":"https:\/\/timotheacreations.com\/?p=1067"},"modified":"2026-09-11T09:02:59","modified_gmt":"2026-09-11T07:02:59","slug":"your-block-theme","status":"publish","type":"post","link":"https:\/\/timotheacreations.com\/fr\/your-block-theme\/","title":{"rendered":"Your block theme is probably loading fonts from someone else&#8217;s server"},"content":{"rendered":"<p class=\"is-style-lead wp-block-paragraph\">I opened the network tab on a theme I had written, and watched it fetch a hundred and twenty font files from a domain I do not own, before a single word appeared on screen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Worse: I was selling that theme with &#8220;local fonts, no CDN&#8221; in the description. I believed it when I wrote it. It was simply not true.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How it gets in<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you have ever added a font through the Site Editor, or started from a theme that shipped a font library, look at your <code>theme.json<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"fontFamilies\": &#91;\n  {\n    \"fontFamily\": \"Abel\",\n    \"slug\": \"abel\",\n    \"fontFace\": &#91;\n      { \"src\": &#91; \"https:\/\/fonts.wp.com\/\u2026\/abel-regular.woff2\" ] }\n    ]\n  }\n]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That <code>src<\/code> is a URL, not a path. WordPress emits an <code>@font-face<\/code> pointing at it, and every visitor&#8217;s browser goes and fetches it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mine had a hundred and twenty-one of those in one theme and a hundred and thirty-three in another \u2014 every weight of every family in a typography library I had bundled for buyers to choose from. And two of them were not optional extras: the <strong>default heading font<\/strong> of one theme and the <strong>default body font<\/strong> of another were both remote. Not an edge case a buyer might hit. The first paint of every site running those themes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The reason it is easy to miss is that everything works. The fonts render. The site looks right. Nothing in your editor tells you the difference between a local face and a remote one, because from the editor&#8217;s point of view there is none.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why it is worth caring about<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>A visitor&#8217;s IP address is personal data.<\/strong> Under GDPR, sending it to a third party requires a lawful basis, and a font that could have been served from the same domain is a hard one to justify. This is not theoretical \u2014 a German regional court awarded damages against a site owner over exactly this in 2022, and the case has been widely reported since. I am a theme author, not a lawyer, and you should treat that as context rather than advice. But the risk is not mine to take on a buyer&#8217;s behalf: <strong>they<\/strong> get the visitors, and <strong>they<\/strong> get the complaint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>It contradicts what a theme claims to be.<\/strong> If you sell on privacy or performance, one CDN URL in <code>theme.json<\/code> undoes the sentence.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>It is a dependency you cannot see fail.<\/strong> A font host having a bad day is your buyer&#8217;s site rendering in Times New Roman, and no error anywhere.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Checking your own theme<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Two greps and one look:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -rn \"fonts.googleapis\\|fonts.gstatic\\|fonts.wp.com\" your-theme\/\ngrep -rn '\"src\"' your-theme\/theme.json | grep http<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then open the site with the network tab filtered to <code>Font<\/code> and check every row&#8217;s domain. The greps catch what is declared; the network tab catches what is actually fetched, including anything a plugin adds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After my fix, both greps return nothing across four themes, and I re-check it before every release \u2014 because it is exactly the sort of thing that creeps back in when you add a font through the editor without thinking.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Doing it properly<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Download the actual files you already reference.<\/strong> Not &#8220;a similar font&#8221; \u2014 the exact families, from the very URLs your theme requests at runtime. Mine were all Google Fonts under the SIL Open Font License, which is what made this a chore rather than a licensing problem.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Put them in the theme and use relative paths.<\/strong> WordPress resolves <code>file:.\/<\/code> against the theme directory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"fontFace\": &#91;\n  {\n    \"fontFamily\": \"Abel\",\n    \"fontWeight\": \"400\",\n    \"fontStyle\": \"normal\",\n    \"src\": &#91; \"file:.\/assets\/fonts\/abel\/abel-regular.woff2\" ]\n  }\n]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Ship the licence.<\/strong> SIL OFL requires the licence text to travel with the fonts. One <code>OFL.txt<\/code> per family, in the family&#8217;s folder. It costs nothing and it is the difference between bundling fonts and appropriating them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. Prefer <code>woff2<\/code> and delete the rest.<\/strong> I found eighteen redundant <code>.ttf<\/code> files sitting beside their <code>woff2<\/code> equivalents. Every current browser takes <code>woff2<\/code>; the <code>.ttf<\/code> was dead weight in every download.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The size question, honestly<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">My themes now carry about thirty font families and a hundred and thirty files each \u2014 roughly 12 MB in the theme folder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That sounds alarming and mostly is not, because <strong><code>@font-face<\/code> is lazy<\/strong>. A declared face is downloaded only if something on the page actually uses it. A visitor to a site using two families downloads two families. The 12 MB is download size for the buyer, once, not page weight for their visitors.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is still a real trade-off and worth being deliberate about. If you bundle a library for buyers to choose from, that is a defensible reason to carry it. If you have thirty families and use three, delete twenty-seven.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The part I got wrong first<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">My first attempt at this &#8220;solved&#8221; it by dropping eleven typography variations and substituting different fonts for the two remote defaults. That is not a fix, it is a redesign \u2014 and it silently changed how every existing customer&#8217;s site looked.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The correct move was the boring one: fetch the exact files, bundle them, change the paths, change nothing visible. A buyer should be able to update and see no difference at all. That is what makes it a fix rather than a new version.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">En bref<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>V\u00e9rifiez <code>theme.json<\/code> for <code>\"src\"<\/code> values that are URLs rather than <code>file:.\/<\/code> paths.<\/li>\n\n\n\n<li>A remote font face sends every visitor&#8217;s IP to a third party before the page paints.<\/li>\n\n\n\n<li>It is easy to miss because nothing breaks \u2014 the fonts render fine.<\/li>\n\n\n\n<li>Download the exact families you already reference, bundle them with their licences, use relative paths.<\/li>\n\n\n\n<li>Keep <code>woff2<\/code>, drop <code>ttf<\/code> duplicates.<\/li>\n\n\n\n<li>Re-run the grep before every release. It creeps back in through the editor.<\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>I opened the network tab on a theme I had written, and watched it fetch a hundred and twenty font files from a domain I do not own, before a single word appeared on screen.<\/p>\n<p>Worse: I was selling that theme with &#8220;local fonts, no CDN&#8221; in the description. I believed it when I wrote it. It was simply not true.<\/p>","protected":false},"author":265843788,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1367,1414],"tags":[],"class_list":["post-1067","post","type-post","status-publish","format-standard","hentry","category-wordpress","category-block-themes"],"_links":{"self":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts\/1067","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/users\/265843788"}],"replies":[{"embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/comments?post=1067"}],"version-history":[{"count":1,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts\/1067\/revisions"}],"predecessor-version":[{"id":1068,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts\/1067\/revisions\/1068"}],"wp:attachment":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/media?parent=1067"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/categories?post=1067"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/tags?post=1067"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}