All posts
Web DevelopmentMar 22, 20268 min read

Next.js Performance Optimization: From 60 to 98 on PageSpeed

The exact techniques we used to take a client's Next.js app from a 60 to a 98 PageSpeed score — covering images, fonts, bundle size, and Core Web Vitals.

C

Cherdung Infotech

Engineering Team

Why PageSpeed Scores Matter

Google uses Core Web Vitals as a ranking signal. A slow site doesn't just frustrate users — it costs you SEO. Here's how we systematically improved a client's Next.js app from a 60 to a 98.

The Audit

Start with data. Run your URL through:

  • [PageSpeed Insights](https://pagespeed.web.dev)
  • Chrome DevTools Lighthouse tab
  • [web.dev/measure](https://web.dev/measure)

The three metrics that matter most: LCP (Largest Contentful Paint), CLS (Cumulative Layout Shift), and FID/INP (interaction responsiveness).

Fix 1: Images

Images are almost always the biggest culprit. Switch every <img> to Next.js <Image>:

import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority // add this for above-the-fold images
/>

This gives you: automatic WebP conversion, lazy loading, correct sizing, and no layout shift.

Fix 2: Fonts

Never load Google Fonts via a <link> tag in Next.js. Use next/font:

import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });

This self-hosts the font, eliminates the render-blocking request, and removes the external DNS lookup.

Fix 3: Bundle Size

Run @next/bundle-analyzer to see what's bloating your JS:

npm install @next/bundle-analyzer
ANALYZE=true npm run build

Common culprits: moment.js (use date-fns), lodash (use individual imports), large icon libraries (import only what you use).

Fix 4: Dynamic Imports

Heavy components that aren't needed on initial load should be lazy-loaded:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('./HeavyChart'), {
  loading: () => <p>Loading...</p>,
  ssr: false,
});

Fix 5: Caching Headers

Add proper cache headers for static assets in next.config.ts:

headers: async () => [
  {
    source: '/images/(.*)',
    headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }],
  },
],

Result

After applying these fixes: LCP dropped from 4.2s to 1.1s, CLS went to 0, and the overall score jumped from 60 to 98. The client saw a 23% increase in organic traffic over the next 60 days.

Need a performance audit for your app? [Talk to us](/contact).

Need help with your project?

We build web apps, mobile apps, and cloud systems for startups and businesses.

Book a free call