April 8, 2026·8 min·By

Next.js Performance Optimization: Making Your App Blazingly Fast

Next.jsperformanceCore Web VitalsoptimizationReact

A slow Next.js app isn't a framework problem -- it's usually a few fixable issues. Here's how to systematically make it fast.

Measure First

Don't optimize blindly:

# Analyze your bundle
npm install @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
    enabled: process.env.ANALYZE === 'true'
})

module.exports = withBundleAnalyzer({
    // your config
})
ANALYZE=true npm run build

The bundle analyzer shows which libraries are taking up space. Usually one or two giant libraries account for 60% of the bundle.

Image Optimization

import Image from 'next/image'

// Good: Next.js Image handles resizing, lazy loading, and format conversion
<Image
    src="/hero.jpg"
    alt="Hero image"
    width={1200}
    height={600}
    priority  // For above-the-fold images
    sizes="(max-width: 768px) 100vw, 50vw"
/>

// For external images, add domain to next.config.js
// images: { domains: ['cdn.example.com'] }

Use priority only for above-the-fold images. Everything else lazy-loads automatically.

Font Loading Without Flash

// app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({
    subsets: ['latin'],
    display: 'swap',  // Show fallback font immediately, swap when loaded
    variable: '--font-inter'
})

export default function Layout({ children }) {
    return (
        <html className={inter.variable}>
            <body className="font-sans">{children}</body>
        </html>
    )
}

next/font downloads fonts at build time and self-hosts them -- no network request to Google at runtime, no FOUT (flash of unstyled text).

Code Splitting and Lazy Loading

import dynamic from 'next/dynamic'

// Lazy load heavy components
const ChartComponent = dynamic(
    () => import('./Chart'),
    {
        loading: () => <div className="h-48 animate-pulse bg-surface rounded" />,
        ssr: false  // Don't render on server (for browser-only libraries)
    }
)

// Load only when user interacts
const [showModal, setShowModal] = useState(false)
const HeavyModal = dynamic(() => import('./HeavyModal'))

return (
    <>
        <button onClick={() => setShowModal(true)}>Open</button>
        {showModal && <HeavyModal />}
    </>
)

Route Prefetching

Next.js prefetches routes in the viewport automatically. Control it explicitly:

import Link from 'next/link'
import { useRouter } from 'next/navigation'

// Prefetch on hover (default)
<Link href="/dashboard">Dashboard</Link>

// Disable prefetch for logged-out pages
<Link href="/premium" prefetch={false}>Premium</Link>

// Programmatic prefetch on hover
const router = useRouter()
<button onMouseEnter={() => router.prefetch('/checkout')}>
    Continue to Checkout
</button>

Minimizing Client JS

// Every 'use client' adds to the JS bundle
// Before adding it, ask: does this need interactivity?

// Bad: made entire page a Client Component just for one button
'use client'
export default function ProductPage({ product }) {
    const [count, setCount] = useState(1)
    return (
        <div>
            <h1>{product.name}</h1>  {/* Doesn't need client */}
            <p>{product.description}</p>  {/* Doesn't need client */}
            <input value={count} onChange={e => setCount(+e.target.value)} />
        </div>
    )
}

// Good: only the interactive part is a Client Component
// ProductPage.tsx (Server Component)
export default function ProductPage({ product }) {
    return (
        <div>
            <h1>{product.name}</h1>
            <p>{product.description}</p>
            <QuantitySelector />  {/* Only this is 'use client' */}
        </div>
    )
}

Core Web Vitals Targets

Metric Good Needs Work Poor
LCP (Largest Contentful Paint) < 2.5s 2.5-4s > 4s
INP (Interaction to Next Paint) < 200ms 200-500ms > 500ms
CLS (Cumulative Layout Shift) < 0.1 0.1-0.25 > 0.25

Fix LCP: Preload your hero image, use next/image with priority, reduce server response time.

Fix INP: Avoid long tasks on the main thread, defer non-critical JS, use web workers for heavy computation.

Fix CLS: Always specify image dimensions, reserve space for ads/dynamic content, avoid inserting content above existing content.

Vercel Speed Insights

// app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next'

export default function Layout({ children }) {
    return (
        <html>
            <body>
                {children}
                <SpeedInsights />
            </body>
        </html>
    )
}

Shows real user LCP, INP, and CLS from actual visitors -- much more accurate than lab tests.

The biggest wins usually come from: analyzing the bundle (often one misconfigured import adds 200KB), switching from <img> to next/image, and moving Client Components to the leaves of the component tree.

K
Founder & Technical Lead, Innovibe

Building software for 15+ years. Passionate about AI, system design, and shipping things that work.

Frequently asked questions

When should I use Server Components vs Client Components?+

Server Components are the default — use them for data fetching, static content, and anything that doesn't need interactivity. Only switch to Client Components when you need browser APIs, state, effects, or event handlers. The less JS you ship to the browser, the faster your site.

Why is my Next.js build so slow?+

Usually it's TypeScript type-checking or a large dependency. Try `NEXT_TELEMETRY_DISABLED=1 next build` with `--profile` and look at what's taking time. Splitting large data files and enabling turborepo are the two biggest wins.

Should I use the App Router or Pages Router for a new project?+

App Router for new projects, full stop. Pages Router still works fine but receives fewer improvements. The learning curve for App Router is steep for one week, then you never want to go back.

Does Innovibe build this kind of thing for clients?+

Yes — this is exactly what we do day-to-day for clients across BC and Canada. If you'd rather have us build and maintain it than implement it yourself, reach out.

How do I decide whether to build this in-house or hire an agency?+

Build in-house if your team has the skills and bandwidth and this is core to your product. Hire out if it's infrastructure, if speed matters, or if the expertise gap would take months to close. We're biased, obviously — but we'll tell you honestly when in-house makes more sense.

Building something with AI?

We scope and ship AI features quickly. Let's talk.

Start a Conversation