CodeWithSabir
HomeAIDevOpsNext.jsMobile DevelopmentWeb Development
CodeWithSabir
  • Home
  • AI
  • DevOps
  • Next.js
  • Mobile Development
  • Web Development
  • About
  • Contact
CodeWithSabir

In-depth articles, tutorials, and guides on web development, React, Next.js, AI, and modern programming practices.

Topics

  • AI
  • DevOps
  • Next.js
  • Mobile Development
  • Web Development

Company

  • About
  • Contact
  • Privacy Policy
  • Terms

© 2026 CodeWithSabir. All rights reserved.

Built with SabirSoft.com

Home/Next.js/Next.js Performance Optimization: 10 Techniques That Make a Real Difference
Next.js

Next.js Performance Optimization: 10 Techniques That Make a Real Difference

Practical Next.js performance optimizations covering image optimization, caching strategies, bundle analysis, lazy loading, and Core Web Vitals improvements that affect Google rankings.

Sabir KhaloufiSabir KhaloufiFebruary 10, 20264 min read

Performance in Next.js isn't just about user experience — Core Web Vitals directly affect Google rankings. A slow site ranks lower. This guide focuses on optimizations that have measurable impact, not micro-optimizations that move the needle by 5ms.

1. Use Next.js Image Component Correctly

The next/image component is one of the biggest wins available — but only if you use it correctly.

tsx
import Image from 'next/image'
 
// WRONG: Fixed dimensions that don't match actual image
<Image src="/hero.jpg" width={800} height={400} alt="Hero" />
 
// CORRECT for hero images: priority + explicit dimensions
<Image
  src="/hero.jpg"
  width={1200}
  height={630}
  alt="Hero image"
  priority              // LCP image — load immediately, no lazy loading
  quality={85}          // Default is 75; 85 is a good balance
  placeholder="blur"    // Show blur while loading
  blurDataURL="data:image/jpeg;base64,..."
/>
 
// CORRECT for below-fold images: lazy loading (default)
<Image
  src={post.thumbnail}
  fill                  // Fills parent container
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt={post.title}
  style={{ objectFit: 'cover' }}
/>

The sizes prop is critical for responsive images — it tells the browser which image size to download based on viewport width. Without it, you download a large image and shrink it via CSS.

2. Optimize Fonts

Next.js has built-in font optimization — it downloads fonts at build time, eliminating the network round trip:

typescript
// app/layout.tsx
import { Inter, Fira_Code } from 'next/font/google'
 
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
})
 
const firaCode = Fira_Code({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-fira-code',
  preload: false,  // Only preload fonts used above-fold
})
 
export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.variable} ${firaCode.variable}`}>
      <body>{children}</body>
    </html>
  )
}

Never load Google Fonts via a <link> tag in Next.js — use next/font/google instead. It eliminates the external network request entirely.

3. Caching Strategies in App Router

The App Router has granular caching controls:

typescript
// Static page — cached indefinitely until manually revalidated
export const revalidate = false
 
// Time-based revalidation — revalidate every 3600 seconds
export const revalidate = 3600
 
// Dynamic page — never cache
export const dynamic = 'force-dynamic'
 
// Revalidate specific data without full page revalidation
async function getPosts() {
  const posts = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 },  // Cache for 1 hour
  })
  return posts.json()
}
 
// Tag-based revalidation — revalidate when specific data changes
async function getPost(slug: string) {
  const post = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { tags: [`post-${slug}`] },
  })
  return post.json()
}
typescript
// When a post is updated, purge just that post's cache
import { revalidateTag } from 'next/cache'
 
export async function updatePost(slug: string, data: PostData) {
  await db.post.update({ where: { slug }, data })
  revalidateTag(`post-${slug}`)  // Only this post's cached pages update
}

4. Parallel Data Fetching

Sequential data fetching creates waterfalls. Fetch in parallel:

typescript
// BAD: Sequential — total time = time(getUser) + time(getPosts) + time(getComments)
export default async function Dashboard({ params }) {
  const user = await getUser(params.id)
  const posts = await getPosts(params.id)      // Waits for user
  const comments = await getComments(params.id) // Waits for posts
  // ...
}
 
// GOOD: Parallel — total time = max(getUser, getPosts, getComments)
export default async function Dashboard({ params }) {
  const [user, posts, comments] = await Promise.all([
    getUser(params.id),
    getPosts(params.id),
    getComments(params.id),
  ])
  // ...
}

5. Route Segment Prefetching

Next.js prefetches routes on hover. For critical navigation paths, prefetch explicitly:

tsx
import Link from 'next/link'
 
// Default: prefetch on hover (good)
<Link href="/dashboard">Dashboard</Link>
 
// Prefetch immediately (for most likely next navigation)
<Link href="/dashboard" prefetch={true}>Dashboard</Link>
 
// Disable prefetching (for rarely visited or heavy pages)
<Link href="/heavy-report" prefetch={false}>View Report</Link>

6. Analyze and Reduce Bundle Size

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

This opens a treemap of your bundle. Common culprits:

  • moment.js — replace with date-fns (tree-shakeable) or dayjs
  • lodash — import specific functions: import debounce from 'lodash/debounce'
  • Large charting libraries loaded on every page

7. Streaming with Suspense

Instead of waiting for slow data before showing anything:

tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
 
export default function DashboardPage() {
  return (
    <div>
      {/* Fast data — loads immediately */}
      <UserHeader />
      
      {/* Slow data — streams in when ready */}
      <Suspense fallback={<RecentOrdersSkeleton />}>
        <RecentOrders />  {/* Hits slow database query */}
      </Suspense>
      
      <Suspense fallback={<AnalyticsSkeleton />}>
        <Analytics />    {/* Hits external analytics API */}
      </Suspense>
    </div>
  )
}

The browser receives the page shell immediately. Slow sections stream in as they resolve. Time To First Byte drops dramatically.

8. Edge Runtime for Global Performance

For lightweight API routes or middleware, the Edge Runtime runs at CDN nodes worldwide:

typescript
// app/api/health/route.ts
export const runtime = 'edge'
 
export function GET() {
  return Response.json({ status: 'ok' })
}

Edge functions have ~0ms cold start (vs ~100-300ms for serverless) and run near the user's geographic location.

Limitations: no Node.js APIs, no native modules. Use for: auth checks, redirects, lightweight APIs.

9. Metadata and OpenGraph Optimization

Complete metadata improves click-through rates from search results:

typescript
// app/post/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug)
 
  return {
    title: post.title,
    description: post.excerpt,
    robots: { index: true, follow: true, 'max-image-preview': 'large' },
    openGraph: {
      title: post.title,
      description: post.excerpt,
      url: `https://yourdomain.com/post/${post.slug}`,
      type: 'article',
      publishedTime: post.createdAt.toISOString(),
      images: [{ url: post.thumbnail, width: 1200, height: 630, alt: post.title }],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.thumbnail],
    },
    alternates: {
      canonical: `https://yourdomain.com/post/${post.slug}`,
    },
  }
}

10. Static Generation for Content Pages

Blog posts, documentation, and marketing pages should be statically generated:

typescript
// app/post/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPostSlugs()
  return posts.map(slug => ({ slug }))
}
 
// Page is generated at build time — serves from CDN, no server compute
export default async function PostPage({ params }) {
  const post = await getPost(params.slug)
  return <ArticleLayout post={post} />
}

Static pages served from a CDN are faster than any server response, and they scale infinitely without load balancer concerns.

Key Takeaways

  • Use priority on your LCP image — it's the single biggest CWV win
  • Always set sizes on responsive images to prevent downloading oversized assets
  • Use next/font/google instead of <link> tags to eliminate external font requests
  • Parallel Promise.all data fetching prevents server-side waterfalls
  • Suspense boundaries let fast content render while slow content streams in
  • Statically generate all content pages — they're faster and cheaper than SSR
#next.js#performance#core web vitals#seo#optimization
Share:
Sabir Khaloufi — author photo

Written by

Sabir Khaloufi

Full-stack developer and tech blogger sharing in-depth tutorials on React, Next.js, AI, and modern web development.

On this page

  • 1. Use Next.js Image Component Correctly
  • 2. Optimize Fonts
  • 3. Caching Strategies in App Router
  • 4. Parallel Data Fetching
  • 5. Route Segment Prefetching
  • 6. Analyze and Reduce Bundle Size
  • 7. Streaming with Suspense
  • 8. Edge Runtime for Global Performance
  • 9. Metadata and OpenGraph Optimization
  • 10. Static Generation for Content Pages
  • Key Takeaways

Related Articles

Next.js

How to Build a Fullstack App with Next.js 15: Complete Guide

A hands-on guide to building a fullstack application with Next.js 15 — covering App Router, Server Actions, database integration, authentication, and deployment.

Sabir KhaloufiFebruary 25, 20265 min read
Next.js

Next.js Server Components Explained: What They Are and Why They Matter

A clear explanation of React Server Components in Next.js — what problem they solve, how they differ from Client Components, and the patterns that make them powerful.

Sabir KhaloufiFebruary 20, 20264 min read
Next.js

Authentication in Next.js with NextAuth.js v5: The Complete Setup

Learn how to implement authentication in Next.js using NextAuth.js v5 — covering credentials, OAuth providers, JWT sessions, protected routes, and role-based access control.

Sabir KhaloufiFebruary 15, 20264 min read