- 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-fontcomponent in the layout, withgoogleFontsURLfor both families; - two
@import url("https://fonts.googleapis.com/…")lines at the top ofglobal.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-sansregisters"Work Sans Variable", so everyfont-familythat 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.cssand so on. The family staysInter, 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:
| Before | After | |
|---|---|---|
| www LCP (mobile) | 3.8 s | 2.4 s |
| www performance | 86 | 95–98 |
| Consulting performance | 88 | 94–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.