Skip to main content

Command Palette

Search for a command to run...

Next.js Performance Optimization

Published
•5 min read•View as Markdown
Next.js Performance Optimization
A

Solving Real-World Problems Through Code.

How We Improved the Performance of a Real-World Next.js Application

Modern users expect web applications to load quickly and feel responsive. Even small delays in loading or interaction can negatively affect user engagement, conversions, and SEO.

Next.js provides strong performance defaults, but in real-world projects, performance often degrades over time due to heavy JavaScript bundles, large images, layout shifts, and unnecessary client-side logic.

In this article, I’ll walk through a real production optimization process, covering:

  • The performance issues we encountered

  • How we identified bottlenecks

  • What key performance metrics actually mean

  • Step-by-step optimizations in a Next.js application

  • Final measurable improvements

This is not a theoretical guide. Every fix discussed here was applied to a live application.


1. Problem Statement

The application showed several performance issues, especially on mobile devices:

  • Slow initial load

  • High JavaScript execution time

  • Large, unoptimized images

  • Layout shifts during page load

  • Heavy third-party scripts

  • Too much logic running globally on the client

PageSpeed Insights and Lighthouse reported the following metrics:

First Contentful Paint (FCP): 3.3s  
Largest Contentful Paint (LCP): 5.0s  
Total Blocking Time (TBT): 10ms  
Cumulative Layout Shift (CLS): 0  
Speed Index: ~3s

Additional warnings included:

  • Reduce JavaScript execution time

  • Serve responsive images

  • Minimize main-thread work

  • Identical links have the same purpose

  • Font display issues

  • Hydration warnings in development

The goal was clear:
Improve perceived load speed, reduce JavaScript cost, and eliminate Lighthouse warnings—without changing the UI or user experience.


2. How We Identified the Issues

Tools Used

Google PageSpeed Insights
Provided Core Web Vitals data and highlighted LCP issues, oversized images, and unused JavaScript.

Lighthouse (Chrome DevTools)
Gave a detailed lab-based performance breakdown, including layout shifts and render-blocking resources.

Chrome Performance Tab
Helped visualize main-thread activity and identify expensive JavaScript execution from third-party libraries.

Next.js Build Output (next build)
Allowed inspection of route-level bundle sizes and shared chunks.

Web Vitals Overlay
Confirmed which elements were responsible for FCP and LCP.


3. Understanding the Core Metrics

First Contentful Paint (FCP)

Measures how quickly the first visible content appears. It’s affected by render-blocking CSS, fonts, and large JavaScript bundles.

Largest Contentful Paint (LCP)

Tracks when the main visible element finishes loading. Common causes of poor LCP include large hero images, slow server responses, and blocking scripts.

Total Blocking Time (TBT)

Represents how long the main thread is blocked by JavaScript. Heavy libraries and large bundles are usually the main contributors.

Cumulative Layout Shift (CLS)

Measures unexpected layout movement. This typically happens when images lack defined dimensions or fonts load late.


4. Step-by-Step Performance Fixes in Next.js

Image Optimization

Problem
Standard <img> tags were loading large images (up to 3000px wide) and resizing them using CSS. Lighthouse flagged this as inefficient.

Solution
Replaced <img> with Next.js <Image>, used the fill layout, and defined responsive sizes.

<div className="relative w-full max-w-[500px] aspect-[5/4]">
  <Image
    src="/home/process.webp"
    alt="Feature image"
    fill
    className="object-cover"
    sizes="(max-width: 1024px) 100vw, 500px"
  />
</div>

Result

  • Smaller image payloads

  • No layout shifts

  • Faster LCP


Font Optimization

Problem
Custom fonts blocked rendering and introduced potential layout shifts.

Solution
Used next/font/google with font swapping and fallback adjustment.

const roboto = Roboto({
  subsets: ["latin"],
  weight: ["400"],
  display: "swap",
  adjustFontFallback: true,
});

Result

  • No invisible text

  • No CLS from font loading

  • Faster FCP


Eliminating Layout Shifts

Problem
Some images and components loaded without reserved space.

Solution

  • Used fixed dimensions or aspect-ratio for visual containers

  • Ensured Next.js Image always reserved layout space

Result
CLS remained at 0 across all pages.


Reducing JavaScript Execution Time

Problem
Heavy libraries were imported globally, including animation libraries, icon packs, and reCAPTCHA.

Lazy-loading animations

const MotionDiv = dynamic(
  () => import("framer-motion").then(mod => mod.motion.div),
  { ssr: false }
);

This ensured animation code loaded only when required.


Scoping reCAPTCHA to specific pages

Instead of loading it globally, we wrapped it in a client-only provider and used it only on the Contact page.

"use client";
import dynamic from "next/dynamic";

const RecaptchaProvider = dynamic(
  () => import("./RecaptchaProvider"),
  { ssr: false }
);

export default function RecaptchaClient({ children }) {
  return <RecaptchaProvider>{children}</RecaptchaProvider>;
}

Optimizing icon imports

// Avoid
import { FaTwitter } from "react-icons/fa";

// Prefer
import FaTwitter from "react-icons/fa/FaTwitter";

This significantly reduced JavaScript parsing and execution time.


Removing Unused CSS

Tailwind’s JIT mode and content scanning removed unused styles automatically, ensuring only the required CSS shipped to production.


Removing Unnecessary JavaScript

Global providers and scripts were moved to page-level usage. Third-party scripts were loaded with next/script and deferred using afterInteractive.


Fixing Accessibility Warnings

Problem

<a href="/products/project360">Project 360</a>
<a href="/pricing/project360">Project 360</a>

Solution

<a href="/products/project360" aria-label="Project 360 product details">
  Project 360
</a>

<a href="/pricing/project360" aria-label="Project 360 pricing">
  Project 360
</a>

This resolved Lighthouse accessibility warnings.


Handling Hydration Warnings

Hydration warnings were traced to browser extensions injecting attributes during development. Testing in Incognito mode confirmed there was no production issue.


5. Final Results

After deploying the optimized production build:

npm run build
npm run start

Performance Improvements

MetricBeforeAfter
FCP3.3s~1.6s
LCP5.0s~2.4s
TBTHighVery Low
CLSRisk0
JS Execution (Mobile)~5s~2s
Lighthouse Score~4585+

6. Key Takeaways

  • Always measure performance in a production build

  • Use Next.js Image for all non-trivial images

  • Lazy-load heavy libraries

  • Scope third-party scripts carefully

  • Avoid global client-side layouts

  • Accessibility issues impact performance scores

  • Measure, optimize, and measure again


Conclusion

Next.js provides a strong performance foundation, but real gains come from deliberate decisions:

  • Efficient image handling

  • Controlled client-side JavaScript

  • Scoped third-party integrations

  • Stable layouts

  • Accessibility best practices

By applying these techniques, we transformed a slow, JavaScript-heavy application into a fast, production-ready Next.js site—without changing a single pixel of the UI.

More from this blog

A

Abhimanyu Payasi Blogs - DevToProd: a tech publication for developers

10 posts

DevToProd: a tech publication for developers building production-ready products. Covering frontend, backend, DevOps, system design, performance, SEO, CI/CD, guides & free tools. — Abhimanyu Payasi