• Oct 5, 2026
  • 3 min read

My LCP Was Render Delay: Self-Hosting Google Fonts with Fontsource

My audit tool flagged this site’s mobile Largest Contentful Paint at 3.8 s. Google’s line is 2.5 s. The usual suspects for a slow LCP are a big hero image or a slow server, and this site had neither.

Read the LCP breakdown before guessing

Lighthouse splits LCP into four phases in the largest-contentful-paint-element audit. From the report JSON:

jq -r '.audits["largest-contentful-paint-element"].details.items[1].items[]
       | "\(.phase) \(.timing|floor)ms"' report.json
TTFB 687ms
Load Delay 0ms
Load Time 0ms
Render Delay 3681ms

The LCP element was a paragraph of text, so there was nothing to download for it. The server answered in under 0.7 s. Render delay was 3.7 s: the browser had everything it needed for the text and still didn’t paint it.

Render delay usually means something render-blocking in <head>. The render-blocking-insight audit lists what:

https://fonts.googleapis.com/css2?family=Work+Sans:[email protected]&display=swap
https://fonts.googleapis.com/css2?family=DM+Sans:opsz,[email protected],100..1000&display=swap
https://www.jalcocertech.com/_astro/BaseLayout.Df2EFRf8.css
https://www.jalcocertech.com/cdn-cgi/scripts/…/email-decode.min.js

The consulting page had the same pattern, with a 3.3 s render delay.

The fonts were loaded twice

Google Fonts stylesheets block rendering: the browser connects to fonts.googleapis.com, downloads the CSS, then connects to fonts.gstatic.com for the font files. On a throttled mobile connection, each new connection costs hundreds of milliseconds.

And this site loaded them twice:

  • an astro-font component in the layout, with googleFontsURL for both families;
  • two @import url("https://fonts.googleapis.com/…") lines at the top of global.css, left over from the template.

Neither was wrong on its own; together they doubled the blocking work. The @import lines were only visible because the built CSS still contained the Google URL after I removed the component.

Self-hosting with Fontsource

Fontsource packages Google Fonts as npm packages with the font files and the @font-face CSS. Importing them from the layout puts both into the site’s own CSS bundle, served from /_astro/ with the rest of the page.

npm i @fontsource-variable/dm-sans @fontsource-variable/work-sans
---
// Fonts are self-hosted (served from /_astro with the page CSS): no render-
// blocking request to Google Fonts, and no third party on every page view.
import "@fontsource-variable/dm-sans/opsz.css";
import "@fontsource-variable/work-sans";
---
<style is:global>
  body { font-family: "Work Sans Variable", sans-serif; }
  h1, h2, h3, h4 { font-family: "DM Sans Variable", sans-serif; }
</style>

Two details:

  • Variable packages rename the family. @fontsource-variable/work-sans registers "Work Sans Variable", so every font-family that said "Work Sans" had to change, including a CSS variable the Mermaid diagrams read.
  • Static packages keep the name. On the consulting page I used @fontsource/inter/400.css, …/700.css and so on. The family stays Inter, so none of the thirteen components that reference it changed.

Then delete the @import lines and the old font component, and check the built output has no Google URLs left:

grep -l "fonts.g" dist/_astro/*.css dist/index.html

Did it work?

I checked in a real browser that the fonts were loaded, not just declared:

document.fonts.check('16px "Work Sans Variable"')  // true

Then I re-ran the audit:

BeforeAfter
www LCP (mobile)3.8 s2.4 s
www performance8695–98
Consulting performance8894–98

There’s a privacy benefit as well: visitors’ browsers no longer contact Google on every page view.

What’s left

The remaining render-blocking item was Cloudflare’s email-decode.min.js, injected because the footer showed an email address. That one is a choice rather than a bug, and I wrote it up separately: a contact link instead of an email in every footer.

Takeaways

  • Read the LCP phases. “Slow LCP” with zero load time and a large render delay points at <head>, not at images or hosting.
  • Look for duplicates. Templates often load fonts in more than one place.
  • Self-host fonts. One fewer origin, no blocking cross-site CSS, and no third-party request on every page.