Back to articles
Performance
8 min
December 19, 2025
Translated with AI

How I think about performance in Next.js

Practical strategies to optimize Next.js applications and achieve perfect Core Web Vitals.

Web performance is not just about "making the site load fast." It is about user experience, conversion, and ultimately, revenue. When we work with Next.js, we have a powerful framework in hand, but it is easy to fall into traps that degrade performance if we don't pay attention to the right details.

The Performance Mindset

Many developers leave performance until the end. "First make it work, then optimize." While premature optimization is a problem, neglecting architectural performance is worse. Performance must be a feature, not a fix.

1. Images are the Number 1 Villain

The next/image component is fantastic, but it doesn't perform miracles if you don't use it correctly.

  • Always use sizes: The most common mistake is failing to define the sizes attribute. Without it, Next.js might serve a huge image to a mobile device.
  • Modern formats: Next.js automatically serves WebP/AVIF, but ensure your original source image has sufficient quality.
  • LCP (Largest Contentful Paint): The image that constitutes the LCP (usually the hero or banner) must have the priority prop. This tells the browser to load it immediately, bypassing the default lazy loading.
// Bad for LCP <Image src="/hero.jpg" width={800} height={600} alt="Hero" /> // Good for LCP <Image src="/hero.jpg" width={800} height={600} alt="Hero" priority />

2. Fonts and Layout Shift (CLS)

Cumulative Layout Shift (CLS) is annoying and penalizes your SEO. Late-loading fonts are frequent culprits.

  • Use next/font: This downloads the font at build time and self-hosts it locally, removing the round-trip to Google Fonts.
  • Use display: swap or optional with well-adjusted fallback fonts.

3. Bundle Size and Dynamic Imports

Next.js automatically performs code splitting per route. But heavy components within a page (like a rich text editor, complex charts, or maps) should be loaded on demand.

import dynamic from 'next/dynamic' const HeavyChart = dynamic(() => import('@/components/HeavyChart'), { loading: () => <p>Loading chart...</p>, ssr: false // If you don't need SEO for this component })

4. Rendering: Server vs Client

The modern golden rule (React Server Components): Move as much as possible to the server.

  • Server Components: Zero JavaScript sent to the client. Great for rendering markdown, fetching data, etc.
  • Client Components: Use only where interactivity is needed (useState, useEffect, event listeners).

The less JS the browser has to download, parse, and execute, the better the TBT (Total Blocking Time) and INP (Interaction to Next Paint).

Conclusion

Performance in Next.js is about leveraging the tools the framework provides (Image, Font, Script) and making conscious decisions about what is rendered where. Monitor your Core Web Vitals and treat performance regressions as critical bugs.