DEV Community

Anas Sheikh
Anas Sheikh

Posted on

How to Make Your Next.js App Blazing Fast — Performance Checklist

A slow Next.js app is almost always caused by the same 6 things.

Here's how to find and fix all of them.


1. Images — The Biggest Performance Win

Images are responsible for most slow page loads.

// 🚫 Regular img — no optimization
<img src="/hero.jpg" alt="Hero" className="w-full" />

// ✅ Next.js Image — automatic WebP, lazy load, size optimization
import Image from 'next/image';

// For images above the fold — add priority
<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority  // preloads — only use for above-fold images
  className="w-full"
/>

// For images below the fold — lazy load by default
<Image
  src="/feature.jpg"
  alt="Feature"
  width={600}
  height={400}
  // No priority — lazy loads automatically
/>
Enter fullscreen mode Exit fullscreen mode

Additional tips:

  • Use WebP format for all images
  • Compress images before uploading — use squoosh.app
  • Never use fill without a sized parent container

2. Fonts — Eliminate Layout Shift

// 🚫 External font link — causes layout shift and render blocking
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />

// ✅ next/font — self-hosted, zero layout shift
import { Inter, Plus_Jakarta_Sans } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',      // show fallback font while loading
  variable: '--font-inter',
});

const jakarta = Plus_Jakarta_Sans({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-jakarta',
});

export default function RootLayout({ children }) {
  return (
    <html className={`${inter.variable} ${jakarta.variable}`}>
      <body>{children}</body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Bundle Size — Stop Loading What You Don't Need

Check your bundle size:

npm install -D @next/bundle-analyzer

# next.config.ts
import bundleAnalyzer from '@next/bundle-analyzer';

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
});

export default withBundleAnalyzer({ /* your config */ });

# Run analysis
ANALYZE=true npm run build
Enter fullscreen mode Exit fullscreen mode

Common fixes:

// 🚫 Importing entire library
import _ from 'lodash';
const result = _.groupBy(items, 'category');

// ✅ Import only what you need
import groupBy from 'lodash/groupBy';
const result = groupBy(items, 'category');

// 🚫 Loading heavy component on every page
import { HeavyChart } from '@/components/HeavyChart';

// ✅ Dynamic import — only loads when needed
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <div className="animate-pulse h-64 bg-slate-700 rounded" />,
  ssr: false, // skip server render for client-only components
});
Enter fullscreen mode Exit fullscreen mode

4. Server Components — Reduce JavaScript Sent to Browser

Every 'use client' component adds JavaScript to your bundle.

// 🚫 Whole page is client JavaScript
'use client';
export default function Dashboard() {
  const [data, setData] = useState(null);
  // fetches on client, adds to bundle
}

// ✅ Server component — zero client JavaScript
export default async function Dashboard() {
  const data = await db.analytics.findMany();
  // fetches on server, sends only HTML
  return <AnalyticsDisplay data={data} />;
}

// Only make interactive parts client components
'use client';
export function AnalyticsDisplay({ data }) {
  const [view, setView] = useState('chart');
  // minimal client JS
}
Enter fullscreen mode Exit fullscreen mode

Rule: only add 'use client' when you need useState, useEffect, or browser APIs.


5. Suspense and Streaming

Don't let one slow query block the whole page:

import { Suspense } from 'react';

export default function Dashboard() {
  return (
    <div>
      {/* Shows instantly */}
      <DashboardHeader />

      {/* Streams in when ready */}
      <Suspense fallback={<StatsSkeleton />}>
        <SlowStats />  {/* fetches its own data */}
      </Suspense>

      {/* Streams in independently */}
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />  {/* fetches its own data */}
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Each Suspense boundary streams independently. User sees content progressively instead of waiting for everything.


6. Metadata and Preloading

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        {/* Preconnect to external domains */}
        <link rel="preconnect" href="https://fonts.googleapis.com" />

        {/* DNS prefetch for API domains */}
        <link rel="dns-prefetch" href="https://api.example.com" />

        {/* Preload critical assets */}
        <link
          rel="preload"
          href="/fonts/inter.woff2"
          as="font"
          type="font/woff2"
          crossOrigin="anonymous"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Performance Checklist

Run through this before every deployment:

Images:

  • [ ] All images use next/image
  • [ ] Above-fold images have priority
  • [ ] Images compressed to WebP

Fonts:

  • [ ] Using next/font not external links
  • [ ] display: 'swap' on all fonts

JavaScript:

  • [ ] Heavy components use dynamic imports
  • [ ] Third party libraries imported selectively
  • [ ] Minimal 'use client' usage

Data fetching:

  • [ ] Slow queries wrapped in Suspense
  • [ ] Static data cached appropriately
  • [ ] No waterfall fetches (fetch in parallel)

Core Web Vitals targets:

  • LCP under 2.5 seconds
  • FID under 100ms
  • CLS under 0.1

Measure First

Don't optimize blindly. Measure first:

# Lighthouse in terminal
npx lighthouse https://yoursite.com --view

# Or use PageSpeed Insights
# https://pagespeed.web.dev/
Enter fullscreen mode Exit fullscreen mode

Fix what Lighthouse flags before anything else. It tells you exactly what's slow and how to fix it.


I applied all these optimizations to my Next.js templates — they all score 95+ on Lighthouse:

Get the templates: https://pixelanas.gumroad.com

What's your biggest Next.js performance win? Drop it below 👇


Anas — full-stack Next.js developer. X: @pixelanas

Top comments (0)