Home
Our WorkBlogContact
Back to Journal
Development May 24, 2026 9 min read

How to Achieve a 100/100 Lighthouse Score with Next.js: A Technical Guide

Abdullah Mubin

Abdullah Mubin

Founder

How to Achieve a 100/100 Lighthouse Score with Next.js: A Technical Guide

We’ve all landed on a website that looks beautiful but takes five seconds to respond to a single click. It’s frustrating, and honestly, it kills conversions before the user even reads your headline. In our line of work at Wizora Studio, speed isn’t a nice-to-have bonus; it’s a core feature. When a founder hires us, they aren't just buying visual layouts—they are buying milliseconds.

Next.js is a fantastic tool, but it doesn't make your site fast out of the box. If you build blindly, you can easily end up with a heavy client-side bundle and a sluggish Lighthouse score. In this guide, I will share the exact optimization blueprint we use to build Next.js applications that load instantly and hit a perfect 100/100 score.

The Core Web Vitals That Actually Matter

Google’s Lighthouse algorithm doesn't care about clean code structure; it cares about user experience. Specifically, it tracks three Core Web Vitals:

  • Largest Contentful Paint (LCP): How fast does the main visual block (usually your hero image or title) render on screen? Under 2.5 seconds is good.
  • Cumulative Layout Shift (CLS): Do elements jump around as the page loads, causing accidental clicks? Your score should be under 0.1.
  • Interaction to Next Paint (INP): When a user clicks a button, does the page react instantly, or is the main thread frozen? Good response time is under 200ms.

Stop Uploading Massive Images

Raw images are the quickest way to ruin your LCP. The Next.js next/image component is great, but developers often make the mistake of leaving out the sizes attribute, forcing browsers to download desktop-resolution assets on a mobile viewport.

Here’s how we set up responsive, high-performance image rendering at Wizora:

import Image from 'next/image';

export default function HeroVisual() {
  return (
    <div className="relative w-full aspect-[16/9]">
      <Image
        src="/hero-mockup.png"
        alt="Sleek interface visualization"
        fill
        priority
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover"
      />
    </div>
  );
}

By marking the image with priority, we instruct the browser to fetch it immediately. For everything else below the fold, Next.js handles lazy-loading automatically. We also enable the AVIF format in next.config.js for better file compression:

module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

Solving Font Jumps (CLS)

Have you ever noticed a flash of unstyled text where headings load in a basic font before switching to your premium typeface? That shifts page layout and triggers layout shift penalties.

With next/font, Next.js downloads Google fonts at build time and embeds them into your static CSS. Here’s our default setup in the root layout:

import { Inter, Outfit } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-sans',
});

const outfit = Outfit({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-display',
});

Next.js calculates the spacing differences between your system font and the custom font, creating a temporary font fallback match. When the custom font loads, there’s no visual shift, and CLS stays at zero.

Defer Non-Critical Javascript

If you import heavy charting libraries or complex animations directly at the top of your page, they get packed into the main bundle. This blocks the browser's thread and delays interactivity.

Use dynamic imports (next/dynamic) to load these scripts only when they are needed on the client-side:

import dynamic from 'next/dynamic';

const InteractiveAnalytics = dynamic(
  () => import('@/components/HeavyChart'),
  { ssr: false, loading: () => <div className="h-64 animate-pulse bg-white/5" /> }
);

This pulls the heavy components out of the initial bundle, making the landing page instantly interactive.

Leverage Edge Caching and Redirects

Server-side rendering (SSR) is great for real-time data, but it takes time for your server to compile database queries. If your response time (TTFB) is slow, you won't hit a perfect 100/100 score.

We use Incremental Static Regeneration (ISR) to cache pages on the edge network. If a page needs redirection or checks for authentication, handle it inside the middleware before compiling page assets:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const session = request.cookies.get('session');
  if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

Manage Third-Party Triggers

Analytics trackers and chat popups block main threads. Load them during browser idle times using Next.js’s script loading strategies:

import Script from 'next/script';

export default function Layout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://www.googletagmanager.com/gtag/js?id=G-TRACKING_ID"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Using strategy="lazyOnload" ensures the script loads during browser idle time, after the page has fully loaded and interactive elements are responsive.

Conclusion

Achieving a 100/100 score isn't a complex secret. It's about being structured, loading only what the user needs immediately, and caching aggressively. If you want us to audit your Next.js setup and optimize your loading times, let’s talk.

Tags: Next.js, Lighthouse, Web Performance, SEO, Image Optimization