Home

Design

UI/UX DesignWeb DesignLanding Page DesignMobile App DesignPitch Deck DesignProduct AuditBrandingRebranding

Development

Web DevelopmentWebflow DevelopmentMVP DevelopmentSaaS DevelopmentCMS DevelopmentMobile App DevSoftware DevelopmentCloud App Development

AI & Automation

AI AutomationAI AgentsChatbot Development
AI AutomationWorkAboutBlogContactBook a Call
Web Development

Next.js Performance Optimization: Get 95+ PageSpeed Scores in 2025

A slow Next.js app is worse than a slow static site — it violates user trust on a platform built for speed. This guide covers every optimization technique to achieve 95+ PageSpeed scores and sub-2-second load times in production.

Suman Mishra

Suman Mishra

Founder & AI Automation Strategist

May 15, 20259 min read
WEB DEVELOPMENTNext.js Performance Optimization: Get 95+PageSpeed Scores in 20259 min read · Codexomation⌨️

Why Performance Matters More Than Ever

Google's Core Web Vitals have been a ranking factor since 2021. But beyond rankings, performance is user experience — and user experience is revenue.

Amazon found that every 100ms of latency cost 1% in sales. Walmart reported a 2% conversion increase for every 1 second of improvement in page load time. These aren't ancient statistics — they're more relevant than ever as user expectations continue rising.

Next.js is built for performance. But Next.js can still be slow if you don't understand which optimizations are defaults, which require explicit configuration, and which common patterns actively harm performance.

Understanding Core Web Vitals

Google measures three Core Web Vitals:

Largest Contentful Paint (LCP): How long until the largest visible element is rendered. Target: under 2.5 seconds. This is usually your hero image, hero text, or main content block.

Cumulative Layout Shift (CLS): How much the page layout shifts during loading. Target: under 0.1. Caused by images without dimensions, dynamically injected content, and web fonts loading.

Interaction to Next Paint (INP): How quickly the page responds to user interactions. Target: under 200ms. This replaced FID (First Input Delay) in 2024.

All three need to pass in "Good" range (75th percentile) for your URLs to be considered "Good" by Google.

Next.js Performance Defaults (Free Wins)

Next.js includes several performance features out of the box. Make sure you're not accidentally disabling them.

Automatic Code Splitting

Next.js automatically splits your JavaScript bundle by route — each page only loads the JavaScript it needs. Don't work against this by importing large libraries at the root level when they're only needed in specific components.

Server Components (App Router)

With the App Router, components default to server components — they render on the server and send only HTML to the client. Zero JavaScript overhead for these components.

// This is a Server Component by default — no "use client" directive
// It renders on the server, sends HTML, zero JS bundle impact
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug); // Direct DB access, no API round-trip
  return <article>{post.content}</article>;
}

Only add "use client" when you actually need browser APIs, React hooks, or event listeners. Client components add to your JavaScript bundle.

Image Optimization

next/image automatically:

  • Converts to WebP/AVIF
  • Resizes for the device requesting it
  • Lazy loads by default
  • Prevents layout shift with required width and height
import Image from "next/image";

// Always specify width and height (or use fill + parent with position:relative)
<Image
  src="/hero.jpg"
  width={1200}
  height={630}
  alt="Hero image"
  priority // Add this to the above-the-fold image (disables lazy loading)
/>

Common mistake: Using <img> instead of next/image. This bypasses all optimization.

Font Optimization

Fonts are one of the most common sources of both CLS and LCP degradation.

Using next/font

import { Space_Grotesk } from "next/font/google";

const font = Space_Grotesk({
  subsets: ["latin"],
  display: "swap", // Prevents invisible text during font load
  variable: "--font-space-grotesk",
  preload: true, // default
});

next/font handles:

  • Self-hosting fonts (no Google Fonts round-trip)
  • Automatic font preloading
  • font-display: swap to prevent invisible text
  • Variable font slicing (only loads the weights you use)

Never do this in App Router:

<!-- Don't import Google Fonts via <link> in layout.tsx -->
<link href="https://fonts.googleapis.com/..." />

This causes an additional network round-trip and can't be preloaded optimally.

CLS from fonts happens when the font loads and causes text reflow. Fix it with:

  1. font-display: swap (already in next/font)
  2. size-adjust CSS property to match fallback font metrics
  3. Or next/font's adjustFontFallback: true option (experimental, but effective)

Bundle Size Optimization

Analyze Your Bundle

npm install --save-dev @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
ANALYZE=true npm run build

This generates a visual map of your bundle. Look for:

  • Libraries that are much larger than expected
  • Duplicate packages (two versions of the same library)
  • Libraries that should be lazy-loaded (charts, rich text editors, date pickers)

Tree Shaking Anti-Patterns

Bad: Import entire library

import _ from "lodash"; // Imports 70kb of lodash
_.debounce(fn, 300);

Good: Import only what you need

import debounce from "lodash/debounce"; // Imports 1.5kb

Even better for simple utilities: Use browser APIs or small focused packages

// Debounce without lodash
function debounce<T extends (...args: unknown[]) => unknown>(fn: T, delay: number) {
  let timer: ReturnType<typeof setTimeout>;
  return (...args: Parameters<T>) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Lazy Loading Heavy Components

Don't load components that aren't visible on first render:

import dynamic from "next/dynamic";

// This component's JavaScript only loads when it's needed
const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <div className="h-64 bg-gray-100 animate-pulse rounded" />,
  ssr: false, // For components that use browser APIs
});

// Lazy load with intersection observer trigger (for below-fold)
const VideoPlayer = dynamic(() => import("./VideoPlayer"), {
  ssr: false,
});

What to lazy load:

  • Chart libraries (Chart.js, Recharts — often 200–300kb)
  • Rich text editors (Tiptap, Quill — 200–400kb)
  • Date/time pickers
  • Video players
  • Map libraries (Leaflet, Google Maps)
  • Analytics and chat widgets

Caching Strategy

Static Generation (Best Performance)

Pages with static data should use Static Generation:

// generateStaticParams makes these routes static at build time
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(p => ({ slug: p.slug }));
}

// No dynamic function = static page
export default function BlogPost({ params }) {
  const post = getPostBySlug(params.slug);
  return <article>{post.content}</article>;
}

Static pages are served from CDN edge nodes — effectively zero time to first byte.

Incremental Static Regeneration (ISR)

For pages that need to stay updated without a full redeploy:

// Regenerates every 60 seconds if requested
export const revalidate = 60;

export default async function ProductPage({ params }) {
  const product = await fetchProduct(params.id); // Fetches fresh data on revalidation
  return <div>{product.name}</div>;
}

Route Segment Config

// Force dynamic rendering (when you need fresh data on every request)
export const dynamic = "force-dynamic";

// Cache the route for 1 hour
export const revalidate = 3600;

Image Optimization Deep Dive

Preloading Critical Images

The hero image should load as fast as possible. Add priority to images above the fold:

<Image src="/hero.jpg" priority width={1920} height={1080} alt="..." />

This adds <link rel="preload"> to the document head, starting the image download earlier.

Responsive Images

<Image
  src="/hero.jpg"
  fill // Parent must have position: relative
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  className="object-cover"
/>

The sizes attribute tells the browser what size the image will be at different viewport widths. This enables correct srcset generation, preventing the mobile browser from downloading a 1920px image for a 390px screen.

Modern Image Formats

In next.config.ts:

const config: NextConfig = {
  images: {
    formats: ["image/avif", "image/webp"],
  },
};

AVIF is 50% smaller than WebP and 80% smaller than JPEG at the same quality. Not all browsers support it yet, but Next.js serves the appropriate format per browser.

Third-Party Script Optimization

Third-party scripts (analytics, chat, ads) are the #1 cause of slow websites that look well-optimized in theory.

Using next/script

import Script from "next/script";

// strategy options:
// - "beforeInteractive": loads before page hydration (use sparingly)
// - "afterInteractive": loads after page hydration (default, most analytics)
// - "lazyOnload": loads during browser idle time (chat widgets, social embeds)
// - "worker": offloads to web worker (experimental, best performance)

<Script
  src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"
  strategy="afterInteractive"
/>

Chat widgets like Intercom should use lazyOnload — they don't need to load until the user is engaged with the page.

Partytown for Analytics

Partytown offloads third-party scripts to a web worker, preventing them from blocking the main thread:

npm install @builder.io/partytown

This can improve INP scores by 30–60ms on analytics-heavy pages.

Database and API Query Optimization

Slow server responses hurt LCP even when the client-side is optimized.

React Cache for Deduplication

import { cache } from "react";

// Multiple components can call this — React deduplicates into one fetch
export const getPost = cache(async (slug: string) => {
  return db.post.findUnique({ where: { slug } });
});

Connection Pooling

Direct database connections don't scale in serverless environments. Use connection pooling:

  • PgBouncer (PostgreSQL)
  • Prisma Accelerate (built-in connection pooling + query cache)
  • Supabase Session Pooler (Supabase users)

Select Only What You Need

// Bad: fetches entire record including large content field
const post = await db.post.findMany();

// Good: only fetch what the listing page needs
const posts = await db.post.findMany({
  select: { slug: true, title: true, excerpt: true, date: true }
});

Measuring and Monitoring

Local Testing

npm run build
npm start
# Lighthouse in Chrome DevTools → Performance tab

Test in production mode. Development mode is intentionally slower.

Real-World Monitoring

Google PageSpeed only gives you a lab measurement. Real User Monitoring (RUM) shows how your actual visitors experience the site.

Tools:

  • Vercel Analytics — built-in Core Web Vitals per URL for Vercel-hosted sites
  • Google Search Console → Core Web Vitals report
  • Sentry Performance — detailed transaction tracing

Performance Budget

Set a performance budget and enforce it in CI:

// lighthouserc.json
{
  "ci": {
    "assert": {
      "assertions": {
        "categories:performance": ["error", {"minScore": 0.90}],
        "first-contentful-paint": ["warn", {"maxNumericValue": 2000}],
        "largest-contentful-paint": ["error", {"maxNumericValue": 2500}]
      }
    }
  }
}

Quick Wins Checklist

Before deep optimization, check these quick wins:

  • All above-fold images use priority prop
  • All images have explicit width and height or use fill
  • Using next/font instead of Google Fonts <link>
  • Heavy components (charts, editors) are lazy loaded with dynamic()
  • Third-party scripts use appropriate strategy
  • next.config.ts enables AVIF/WebP formats
  • Database queries use select to fetch only needed fields
  • Static pages use generateStaticParams
  • No "use client" on components that don't need it

If your Next.js site is underperforming and you need expert optimization, reach out to our team — we audit and optimize Next.js applications for both performance and SEO as a standalone service.

Ready to get started?

Let's build something great together

Book a free strategy call with our team — no commitment, no fluff. Just clarity on what's possible for your project.

Book a Free Call →
Share𝕏in
#Next.js#performance optimization#Core Web Vitals#PageSpeed#web performance

Want help with this? We build it.

Explore Web Development Services

Related Articles