Journal

My "Lightweight" Next.js Site Scored 59 on Lighthouse. Here's What Actually Fixed it.

Learn how to increase your overall website performance. Loading speed and experience are huge factors for getting organic results on Google. Learn how to address stubborn load time issues with websites.

Tyler Lazenby
Tyler LazenbyClevoro President and Owner
My "Lightweight" Next.js Site Scored 59 on Lighthouse. Here's What Actually Fixed it.

Intro

My site is light. The server responds in a blazing 20ms. So why did Google Lighthouse hand me a soul-crushing 59?

That was the exact paradox sitting on my screen last week. Server-Side Rendering was dialed in, edge functions were flying, and static routes resolved almost instantaneously. Yet Lighthouse flagged my site with a muddy yellow-orange score.

The problem stems from a fundamental misunderstanding of web performance: a fast server does not equal a fast user experience. Time to First Byte (TTFB) is just opening the door; what happens inside the browser main thread once the client receives the HTML is where performance scores go to die.

How to Actually read a Lighthouse Report

Most people see the big red or yellow score, curse under their breath, and immediately start re-engineering their API routes or stripping CSS libraries.

That score is a symptom; the audits are the diagnosis.

To actually fix performance, stop staring at the main number and jump straight into the Diagnostics and Opportunities sections:

  1. Expand "Largest Contentful Paint element": Locate the exact DOM node holding up visual rendering.
  2. Inspect "Reduce JavaScript execution time": Identify which third-party script is hogging thread time.
  3. Check "Properly size images": Calculate how many unnecessary megabytes are being shipped to mobile viewports.

Once you read the report as a trace rather than a report card, the primary culprits become obvious.

Suspect #1: The LCP image

Largest Contentful Paint (LCP) measures how long it takes for the main hero element above the fold to render.

A common oversight is serving a raw 1280×960 PNG cover photo inside a visual card displayed at 362px on a mobile viewport. Browsers are forced to download an uncompressed payload over mobile networks and scale it down on the fly.

To resolve this in Next.js:

  • Pass the priority prop to preload the hero asset in the document <head>.
  • Set accurate sizes so the browser requests the appropriate asset width.
  • Route images through Sanity or CDN transforms to deliver WebP/AVIF formats dynamically.

Before: Unoptimized Image Tag

TypeScript
// ❌ Unoptimized image fetch causing delayed LCP
<img 
  src={post.mainImage.url} 
  alt={post.mainImage.alt} 
  className="w-full h-auto" 
/>

After: Optimized Next.js Image

TypeScript
// ✅ Optimized LCP image with priority and responsive sizing
import Image from 'next/image';
import { urlFor } from '@/lib/sanity';

<Image
  src={urlFor(post.mainImage).width(800).format('webp').url()}
  alt={post.mainImage.alt}
  width={800}
  height={450}
  priority
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
  className="w-full h-auto object-cover"
/>

Suspect #2: The third-party tax

AdSense and Google Analytics often weigh more than an entire lightweight codebase combined.

Adding an async tag to third-party scripts is not enough. An async script still downloads with high priority and executes as soon as it arrives, hijacking the single browser thread while parsing heavy JavaScript bundles.

To reclaim main thread performance:

  1. Establish preconnect links in your <head> to establish early DNS and TLS connections.
  2. Defer non-critical analytics and ad scripts using the next/script component with afterInteractive or lazyOnload strategies.

Resource Preconnect Snippet

HTML
<!-- Establish early handshakes with critical third-party origins -->
<link rel="preconnect" href="https://www.googletagmanager.com" />
<link rel="preconnect" href="https://pagead2.googlesyndication.com" crossorigin="anonymous" />

Next/Script Deferred Loading Snippet

TypeScript
import Script from 'next/script';

export default function ThirdPartyScripts() {
  return (
    <>
      {/* Load GA4 after the page becomes interactive */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
        strategy="afterInteractive"
      />
      
      {/* Defer ad scripts until idle window */}
      <Script
        src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX"
        strategy="lazyOnload"
        crossOrigin="anonymous"
      />
    </>
  );
}
The Fixes, Ranked by ROI
FixEffortExpected Gain
Add priority & sizes to LCP ImageLow (5 mins)+15 to +25 pts
Defer Ads / Non-essential Scripts (lazyOnload)Low (10 mins)+10 to +20 pts
Convert Images to WebP/AVIF via CDNLow (15 mins)+5 to +10 pts
Add Preconnect Tags for Foreign DomainsMinimal (2 mins)+3 to +8 pts
Font Subsetting & font-display: swapMedium (20 mins)+2 to +5 pts
The Fixes, Ranked by ROI

Results

Addressing asset delivery and execution order significantly changes the metric profile:

  • Before Fixes: Lighthouse Score 59 | LCP 4.2s | Total Blocking Time 680ms
  • After Fixes: Lighthouse Score 96 | LCP 1.1s | Total Blocking Time 40ms

No core architectural overhaul or framework swap was required—just explicit resource prioritization.

Wrapping Up

The uncomfortable truth about web development today is that ads and tracking scripts fund the ecosystem while directly penalizing performance scores. Google sits on both sides of that table—docking search rankings for poor Core Web Vitals while providing the very ad scripts that degrade them.

You don't need to strip away functionality to achieve high scores. By taking control of browser loading priorities, you can deliver fast user experiences without sacrificing monetization or analytics.

1 Comment

Sign in to join the discussion.

  • Tyler Lazenby

    What big changes have you seen that worked for you?