Skip to content

Critical CSS and Font Loading: The Last 200ms of LCP

Once you’ve fixed images and JS, font and CSS strategy is the next LCP lever. Here’s the pattern.

John Cravey with AIFounder4 min readUpdated Jul 6, 2026

After the obvious LCP wins (optimize images, defer JS), the next 100-200ms usually hides in how the browser handles CSS and fonts during initial render. Most teams either ignore this layer or over-engineer it. Here’s the pattern that earns the last 200ms without adding maintenance pain.

Free estimate · 2 minutes

Slow pages lose rankings and leads. Price the fix.

Build the estimate to see what a fast rebuild would look like for your site and what we would ship in the first thirty days.

Why CSS blocks rendering

Browsers won’t paint until they have the CSS needed to lay out the visible content. Every external stylesheet linked in the head delays the first paint by however long that stylesheet takes to download + parse. Inline critical CSS in the head, async-load the rest, and your first paint can be hundreds of ms faster.

Critical CSS extraction

Critical CSS is the subset of your stylesheets needed to render above-the-fold content. Inline it in the document head. The rest of the CSS loads asynchronously and doesn’t block first paint.

Tools that extract it automatically: critical (Node), critters (Webpack plugin, default in some frameworks), beasties (more recent alternative). All work by rendering the page in a headless browser, identifying which CSS rules applied to visible elements, and outputting that subset.

Next.js and critical CSS

Next.js 14+ with Tailwind 4 handles this well by default — the CSS for the route is inlined into the HTML response for static routes. You don’t need a separate extraction step. For larger CSS-in-JS setups or for routes that pull in heavy component CSS, the manual extraction is still worth it.

Font loading: the underrated LCP lever

Web fonts are heavy (50-200KB per face). If your headline uses a custom font, the LCP element doesn’t render until the font has loaded — that’s often 300-600ms of unnecessary wait. The fixes:

  1. Limit to two font families maximum, two weights per family. Every additional weight is another file.
  2. Preload the font files in the head (`<link rel="preload" as="font">`).
  3. Use font-display: swap or font-display: optional in the @font-face rule. Swap renders fallback text immediately and swaps when the font arrives; optional uses fallback if the font isn’t cached.
  4. Use size-adjust and other font-metric overrides to make the fallback render with similar metrics to the custom font, eliminating layout shift on swap.

next/font: the easy mode

Next.js ships `next/font` which handles preloading, font-display, and size-adjust automatically. For Google Fonts:

// app/layout.tsx
import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  weight: ["400", "600", "700"],
  variable: "--font-inter",
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.variable}>
      <body>{children}</body>
    </html>
  );
}

next/font self-hosts the font files (no external request to Google’s CDN), preloads them automatically, and generates the size-adjust overrides for you. Bigger LCP win than you’d expect.

Custom fonts (not from Google)

next/font supports local files via `localFont()`. Pass the woff2 file, the same display/preload options. Same wins.

import localFont from "next/font/local";

const heading = localFont({
  src: [
    { path: "./fonts/Heading-Regular.woff2", weight: "400", style: "normal" },
    { path: "./fonts/Heading-Bold.woff2", weight: "700", style: "normal" },
  ],
  display: "swap",
  variable: "--font-heading",
});

Avoiding layout shift on font load

When a fallback font has different metrics than the custom font, the text reflows when the custom font arrives — Cumulative Layout Shift. CSS `size-adjust`, `ascent-override`, `descent-override`, `line-gap-override` let you tune the fallback to match the custom font’s metrics so the swap is invisible.

next/font computes the overrides automatically. For hand-rolled @font-face rules, use font-metric tools to compute the right overrides.

What to do with above-the-fold images

If your hero is an image, it’s usually the LCP element. Three things to set:

  • `priority` on next/image — preloads the source.
  • Correct `sizes` attribute so the right resolution loads.
  • AVIF/WebP format — next/image handles automatically.

What to do with above-the-fold text

If your hero is text (a big headline + dek), the LCP element is the headline. The lever is font loading. Inline the font in the critical CSS path. Preload the font file. Use font-display: swap so the text appears before the font is fully loaded — visible immediately, restyled when ready.

The 100ms wins compound

  • Self-host fonts (no third-party DNS / TCP): 50-150ms saved.
  • Preload critical font files: 100-200ms saved.
  • Inline critical CSS: 50-150ms saved.
  • Async-load non-critical CSS: 50-100ms saved.
  • Use size-adjust to eliminate CLS on font swap: zero LCP impact but CLS dramatically improves.

Add them up: an LCP that was 2.4s drops to 1.8s after a couple of evenings of focused work. The work isn’t glamorous but it’s the work that moves Core Web Vitals into ‘good’ on borderline sites.

Measuring

Open Chrome DevTools, Performance tab, record a page load. Look at the Timing track. The LCP marker shows when LCP fired. Trace backwards: what loaded just before LCP? That’s the critical resource. Optimize it.

When this isn’t enough

If your LCP is still 2.5s+ after these optimizations, the issue isn’t CSS or fonts — it’s probably the origin server, the image strategy, or render-blocking JavaScript. Audit those layers separately. The CSS/font work is the last 200ms, not the first second.

How this lands across FH client work

Every FH client site uses next/font with the right font-display, preload, and size-adjust setup. Critical CSS is handled by Next.js automatically for our static routes. Hero images use next/image with priority. The combination consistently delivers under-2s LCP on mobile across the client book. If your site’s LCP is borderline and you’re not sure where to find the last 200ms, book a consultation — the audit usually finds 3-4 specific wins.

Answers

Frequently asked questions

What is critical CSS?

The small subset of styles needed to render what is visible on load, inlined so the browser can paint without waiting for a stylesheet. It targets the render-blocking gap at the start of a page load, which is where the last stubborn part of the loading metric usually hides.

Why do web fonts hurt the loading metric?

Because text cannot paint in its final form until the font arrives, and a font is a separate request discovered after the CSS. If the largest visible element is text, the font is on the critical path whether or not anyone thought of it that way.

What does font-display swap do?

It renders text immediately in a fallback and swaps when the web font loads, so content is readable rather than invisible during the wait. The trade is a visible reflow, which is worth it in almost every case, because unreadable text is a worse experience than a flicker.

Should fonts be preloaded?

The one or two used above the fold, yes, so the request starts immediately rather than after the stylesheet is parsed. Preloading every font makes things worse by competing for bandwidth with the content, which is the common overcorrection.

How many font weights should a site load?

Two in most cases, or a single variable font. Each additional weight is another file on a path that already matters. Design systems specifying five weights are usually describing a preference rather than a requirement, and the difference is measurable in load time.

Should fonts be self-hosted or loaded from a provider?

Self-hosted, almost always. It removes a third-party connection from the critical path, avoids an extra DNS and TLS negotiation, and gives you control over caching. The convenience of a hosted stylesheet costs more than it saves on a page where the font is on the critical path.

How much does this actually save?

The last couple of hundred milliseconds, which is exactly the gap that separates a page just over the threshold from one just under it. It is not where you start on a slow site, and it is often what finishes the job on a nearly-fast one.

What is the risk of inlining critical CSS?

It grows the HTML on every request and it goes stale when the design changes, so extracting it by hand once is a trap. Generate it as part of the build, or accept a small hand-maintained subset for the header only and keep it deliberately tiny.

Does this matter if the site is already fast?

Less, and it is still the right last step when the loading metric sits close to the threshold. On a site comfortably under target, this work is optimization for its own sake and the effort belongs somewhere with a business return.

What about system fonts?

They eliminate the problem entirely: no request, no swap, no flicker. If the brand does not depend on a specific typeface, a system stack is the fastest possible answer and it costs nothing to adopt. That is a design decision rather than a technical one.

How do I know fonts are on the critical path?

Check what the largest visible element is. If it is text, the font matters. If it is a hero image, fix the image first and the font second, because effort spent on the wrong one of those two produces no measurable change.

What is the order of work here?

Reduce the number of font files, self-host them, preload only what is above the fold, set the display behaviour so text is never invisible, then consider critical CSS. Doing critical CSS first is the common mistake, because it is the most complex and the least reliable.

Question we did not answer? Ask us directly and we will answer it here.

John Cravey, Founder
Written by
John Cravey
Founder

Founder of Frontend Horizon. Writes most of the long-form work on the FH blog.

Newer post
Cloudflare WAF and Bot Management for SMB Sites: The Rules That Actually Work
Older post
Why Your Website’s Bounce Rate Is Lying to You
Keep reading

More from the blog

SEO·10 min

Automated Technical SEO Audits: Crawl, Score, and Fix With AI

A once-a-year audit finds problems a year too late. The point of automating it is that it never stops looking.

Next.js·10 min

Speed Is a Ranking Factor: The Next.js Performance Checklist by Business Size

Google measures your speed with real users and ranks you on it. Next.js hands you the tools to win. Most sites leave them switched off.

Performance·4 min

Core Web Vitals 2026: The Metrics That Matter and the Targets That Hold

Three numbers. Hit them and you’re competing on content; miss them and you’re competing one hand tied.