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
widthandheight
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: swapto 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.
Eliminating Font-Related CLS
CLS from fonts happens when the font loads and causes text reflow. Fix it with:
font-display: swap(already innext/font)size-adjustCSS property to match fallback font metrics- Or
next/font'sadjustFontFallback: trueoption (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
priorityprop - All images have explicit
widthandheightor usefill - Using
next/fontinstead of Google Fonts<link> - Heavy components (charts, editors) are lazy loaded with
dynamic() - Third-party scripts use appropriate
strategy -
next.config.tsenables AVIF/WebP formats - Database queries use
selectto 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 →Want help with this? We build it.
Explore Web Development Services →Related Articles
SEO for SaaS Companies: The Programmatic Content Strategy That Drives 10x Growth
The SaaS companies winning on organic search in 2025 aren't just blogging more. They're building programmatic SEO machines, establishing topical authority, and treating content as product. This is how to build an organic moat that compounds for years.
CMS Development in 2025: Choose the Right Content Platform for Your Business
The wrong CMS choice creates years of technical debt. WordPress sites get hacked, headless CMSes confuse marketing teams, and Webflow runs into limits. This guide helps you choose the right platform and build it correctly.
How to Hire a Web Development Agency: The 2025 Guide to Not Getting Burned
Hiring the wrong web development agency is one of the most expensive mistakes a business can make. This guide shows you exactly how to evaluate agencies, structure the engagement, and protect yourself from the most common disasters.
