DEV Community

BeanBean
BeanBean

Posted on • Originally published at nextfuture.io.vn

Next.js Performance Optimization: The 2026 Complete Guide

Originally published on NextFuture

Your Next.js App is Slower Than It Should Be

You've built a beautiful Next.js app. TypeScript is clean, components are organized, the design looks sharp. But Lighthouse shows a Performance score of 62, your LCP is 4.2 seconds, and users on mobile are bouncing. Sound familiar? Next.js gives you the tools to build blazing-fast apps — but only if you use them correctly. This guide covers the optimization techniques that actually move the needle in 2026.

1. Default to React Server Components

The single highest-impact change you can make: stop defaulting to "use client". Every client component ships JavaScript to the browser. Server components render on the server and send only HTML.

// ❌ Unnecessary client component — no interactivity needed
"use client";
import { ProductList } from "@/components/ProductList";

export default async function ShopPage() {
  const products = await fetchProducts(); // Can't do this in client component anyway
  return 
;
}

// ✅ Server component — zero JS sent to browser
import { ProductList } from "@/components/ProductList";

export default async function ShopPage() {
  const products = await fetchProducts(); // Direct DB/API call, no useEffect needed
  return ;
}
Enter fullscreen mode Exit fullscreen mode

Only add "use client" when you need: event handlers, browser APIs, useState, or useEffect. Everything else should be a server component.

2. Leverage Next.js Caching Layers

Next.js 15 has four distinct caching mechanisms. Most developers only use one. Here's how to use all of them:

// Request Memoization — deduplicates identical fetches in one render
const user = await fetch(`/api/users/${id}`, { cache: "no-store" });

// Data Cache — persists across requests (like ISR)
const products = await fetch("/api/products", {
  next: { revalidate: 3600 }, // Revalidate every hour
});

// Full Route Cache — static pages cached at build time
// Happens automatically for pages with no dynamic data

// On-demand revalidation — bust cache when data changes
import { revalidatePath, revalidateTag } from "next/cache";

export async function updateProduct(id: string, data: ProductData) {
  await db.product.update({ where: { id }, data });
  revalidateTag(`product-${id}`); // Bust specific cache entries
  revalidatePath("/shop"); // Bust the shop page
}
Enter fullscreen mode Exit fullscreen mode

Proper caching can eliminate 80% of your database calls without any architectural changes.

3. Optimize Images the Right Way

The <Image> component from next/image handles WebP conversion, lazy loading, and responsive sizing automatically. But most developers miss the priority and sizes props:

import Image from "next/image";

// Hero image — mark as priority to preload (improves LCP)

// Product grid images — proper sizes hint prevents oversized downloads

Enter fullscreen mode Exit fullscreen mode

The sizes attribute tells the browser which image size to download based on viewport width. Getting this right alone can cut image payload by 40-60%.

4. Split Bundles with Dynamic Imports

Heavy libraries loaded on the initial render inflate your JavaScript bundle. Use dynamic imports to load them only when needed:

import dynamic from "next/dynamic";

// Load heavy chart library only when component is visible
const RevenueChart = dynamic(() => import("@/components/RevenueChart"), {
  loading: () => ,
  ssr: false, // Charts often require browser APIs
});

// Load modals only when triggered
const EditProductModal = dynamic(() => import("@/components/EditProductModal"), {
  loading: () => null,
});
Enter fullscreen mode Exit fullscreen mode

For a typical dashboard app, dynamic imports can reduce initial bundle size by 30-50%.

5. Deploy to Vercel Edge for Maximum Speed

Your app's performance ceiling is set by where it runs. Vercel's Edge Network runs your code in 100+ locations worldwide — typically within 20ms of any user. Deploy your Next.js app with zero configuration:

# Install Vercel CLI
npm i -g vercel

# Deploy from your project root
vercel --prod

# Set environment variables
vercel env add DATABASE_URL production
Enter fullscreen mode Exit fullscreen mode

Vercel automatically enables: Edge caching, automatic HTTPS, image optimization CDN, and Analytics for Core Web Vitals tracking. For most Next.js apps, moving from a VPS to Vercel improves TTFB by 200-500ms globally.

6. Analyze and Eliminate Bundle Bloat

Before optimizing, measure. Add @next/bundle-analyzer to find what's bloating your bundle:

npm install @next/bundle-analyzer
ANALYZE=true npm run build
Enter fullscreen mode Exit fullscreen mode

Common culprits: moment.js (switch to date-fns or Temporal API), lodash (import individual functions), and unoptimized icon libraries (use only what you import).

Performance Optimization Checklist

✅ Default to server components; use client components sparingly

  • ✅ Set revalidate or cache tags on all data fetches

  • ✅ Add priority to above-the-fold images

  • ✅ Use sizes prop on all responsive images

  • ✅ Dynamic-import heavy components and libraries

  • ✅ Deploy on Vercel for global edge distribution

  • ✅ Run bundle analyzer before every major release

The 90+ Score is Achievable

Performance isn't magic — it's a series of deliberate decisions. Apply these techniques systematically and a Lighthouse score above 90 is realistic for most Next.js apps. The best part: most of these optimizations require changing only how you use the framework, not rewriting your architecture. Start with server components today and work down the list.


This article was originally published on NextFuture. Follow us for more frontend & AI engineering content.

Top comments (0)