Next.js Font Optimization Broke Our Client's Largest Page Load cover image
Back to Blog
TechnologyPublished 30 August 2026ยท Updated 30 August 2026ยท 5 min read

Next.js Font Optimization Broke Our Client's Largest Page Load

A routine hero headline update caused our LCP to spike above 3.5s on mobile. The fix was swapping two static Inter weights for one variable font with manual fallback metrics.

The Incident: LCP Spike on the Academy Labs Marketing Site

In early 2026, our marketing site at Agentic Academy Labs, built with Next.js 14 and deployed on Vercel, started showing p75 LCP values above 3.5s on mobile devices, according to Vercel Analytics. The site had previously scored well in local Lighthouse runs (LCP under 1.5s). The regression appeared after a routine deploy that added a new hero headline using Inter 700 via next/font/google.

We use the marketing site to attract interns and showcase client work. The hero headline is the first thing visitors see. When LCP crossed the 2.5s threshold, our conversion tracking flagged a drop in form submissions, especially on Android devices on Slow 4G.

The deploy diff was small. We replaced a system-font headline with Inter 700 for brand consistency. The font loaded from Google Fonts through next/font/google, which is the recommended path per the Next.js docs Getting Started: Font Optimization.

Locally, everything looked fine. Lighthouse on our MacBooks showed LCP under 1.2s. But Vercel Analytics told a different story. Real users on real devices were waiting longer for the headline to paint.

What We Tried and What Failed

We initially assumed the issue was image-related. We optimized our hero image further, cutting it down to 80KB and adding loading="eager". No change in LCP.

Next, we tried adding font-display: optional to the Inter import, hoping to avoid FOIT (Flash of Invisible Text). The idea was that if the font didn't load within 100ms, the browser would skip it and use the fallback. But this made the headline render in Times New Roman on first load, which looked worse and did not improve LCP.

We then attempted to preload all font weights manually using <link rel="preload"> tags in head.tsx. This actually increased the HTML payload and made things worse. The preload tags added bytes to the critical path, and the browser spent time parsing them instead of painting content.

The real problem was not the loading strategy but the mismatch between fallback font metrics and the actual font metrics for non-default weights. next/font computes size-adjust, ascent-override, and descent-override against the first imported weight. We had imported Inter 400 and 700. The overrides matched 400. Our H1 in 700 still shifted when the real font arrived.

The Working Fix: Single Variable Font with Manual Fallback Metrics

We replaced the static Inter 400 and 700 imports with a single Inter variable font file (Inter-roman.var.woff2) stored in app/fonts/. We used next/font/local with explicit weight: '100 900' and manually set size-adjust, ascent-override, and descent-override based on the 400 weight.

We also disabled automatic preloading for non-critical font families using preload: false.

Here is the config we landed on in app/fonts/inter.ts:

import localFont from 'next/font/local';

export const inter = localFont({
  src: [
    {
      path: '../fonts/Inter-roman.var.woff2',
      weight: '100 900',
      style: 'normal',
    },
  ],
  variable: '--font-inter',
  preload: true,
  display: 'swap',
  sizeAdjust: '100.0145%',
  ascentOverride: '95.8%',
  descentOverride: '24.5%',
});

We generated the WOFF2 file using woff2_compress from the woff2 package and placed it in app/fonts/. The variable font covers weights 100 through 900 in a single file, eliminating the need for multiple requests.

After deploying, p75 LCP dropped to 1.8s and CLS stabilized at 0.02. The hero headline now paints quickly and stays put when the font loads.

Pitfalls We Would Warn an Intern About

  • Do not assume next/font handles all fallback metric adjustments correctly across weights. It only computes them against the first imported weight.
  • Avoid importing multiple static weights of the same family unless absolutely necessary. Each weight adds a separate request and increases the chance of layout shift.
  • Never trust local Lighthouse scores alone. Always test with throttled network conditions and cold caches.
  • Do not manually add <link rel="preload"> tags for fonts unless you are certain they are on the critical path. Over-preloading can bloat HTML and delay LCP.

We learned this the hard way. A designer added a font-weight: 800 headline to a landing page, and CLS jumped from 0.02 to 0.18 overnight. Nobody recomputed the fallback metrics.

What We Would Do Differently Next Time

Next time, we would audit font usage during the design phase and mandate the use of variable fonts from the start. We would also set up automated performance regression checks in CI that include real-device Lighthouse runs with Slow 4G throttling.

Additionally, we would scope brand fonts to specific layouts rather than loading them globally in the root layout. The marketing site uses Inter everywhere, but our internship portal uses a different system font stack. Loading both globally was wasteful.

We would also vendor the WOFF2 files locally instead of relying on Google Fonts, even through next/font/google. This gives us full control over caching and eliminates any external dependency.

The Broader Lesson

Fonts are one of the most impactful performance decisions you will make for a web app, and they are also one of the most ignored peal.dev.

If your LCP element is text and your p75 LCP is above 2.5s, fonts are usually involved. If CLS spikes are coming from text nodes shifting on font load, your fallback metrics are wrong, check adjustFontFallback or your manual size-adjust values.

The cheap audit is simple. Open production in DevTools with the network throttled to Slow 4G, record a cold load, and count the font requests in the waterfall. If there are more than two, you have work to do. Trim subsets and weights, consolidate to a single variable font where you can, scope brand fonts to the layouts that need them, and verify the preload tag is actually in the HTML response.

We have seen this drop LCP by 200-400ms on slower connections in our own builds 72Technologies.

The fix is to turn off automatic preload on the secondary families and use a single variable font with manual fallback metrics. It is not glamorous, but it works.

Sources

Enjoyed this article?

Back to Blog