Skip to main content
← Back to Blog10 min read

Optimizing Next.js Performance

Next.jsPerformanceReact

Practical tips for improving Core Web Vitals in Next.js applications, including image optimization and dynamic imports.

I. Rendering Strategies and Data Fetching

The method you choose to render your page directly impacts the Time to First Byte (TTFB) and Largest Contentful Paint (LCP).

A. Utilize Server Components by Default (App Router)

In Next.js's App Router, components are Server Components by default, meaning they run entirely on the server and are rendered to HTML before being sent to the browser.

Component TypeExecution EnvironmentPerformance BenefitWhen to Use
Server ComponentsServer (Zero client JS)Reduces Client JavaScript Bundle Size and hydration time. Direct access to server resources (DB, files).Data fetching, static UI, layouts, and non-interactive components.
Client ComponentsClient (Browser)Enables interactivity.State (useState), effects (useEffect), event handlers (onClick), and browser APIs (window, localStorage).

Best Practice: Apply the 'use client' directive to the smallest necessary boundary. Nesting Server Components within a Client Component (passed as children) keeps the deeply nested components server-rendered and lightweight.

B. Strategic Data Caching and Regeneration

Next.js offers robust caching mechanisms that should be leveraged over simple Server-Side Rendering (SSR) where possible.

  • Static Site Generation (SSG): Pages generated at build time and served as static HTML. Provides the fastest possible load time. Use for pages with content that changes rarely (e.g., marketing pages, documentation).
  • Incremental Static Regeneration (ISR): Generates static pages but allows them to be updated periodically after deployment using the revalidate option in getStaticProps (Pages Router) or fetch options (App Router).

Logic: This combines the speed of static serving with the freshness of dynamic data. The older cached version is served while a new one is generated in the background.

II. Asset Optimization: Images, Fonts, and Scripts

Unoptimized assets are a primary cause of high LCP and Cumulative Layout Shift (CLS).

A. The <Image> Component

The built-in next/image component automates several critical performance steps.

Optimization FeatureHow to ImplementMetric Improved
Automatic Sizing/CLS PreventionAlways define width and height props for remote images (or use the fill prop within a dimensioned container).Lowers CLS.
Lazy LoadingEnabled by default. Images below the fold only load when they approach the viewport.Lowers initial page weight and improves LCP.
Priority LoadingSet priority={true} for images above the fold (e.g., hero image).Improves LCP by fetching the critical image immediately.
Modern FormatsHandled automatically. Next.js serves formats like WebP or AVIF if the browser supports them.Reduces image file size.

B. Font Optimization with next/font

Use the next/font module (e.g., google or local) to automatically host font files with your static assets, eliminating external network requests.

It automatically applies font-display: optional or swap to ensure text remains visible during font loading, preventing a blank screen (Flash of Invisible Text, or FOIT).

C. Third-Party Scripts

Use the next/script component to manage external scripts (like analytics or ads) and prevent them from blocking the main thread.

import Script from 'next/script';

<Script
  src="https://third-party-analytics.com/script.js"
  strategy="afterInteractive" // Loads after the page is interactive.
/>

III. Bundle Size and Code Splitting

Keeping the initial JavaScript bundle small is crucial for fast Time To Interactive (TTI).

Dynamic Imports

Use next/dynamic to lazy-load components that are not critical for the initial render (e.g., modals, off-screen charts, complex admin panels).

import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('../components/Chart'), {
  ssr: false, // Optional: Force rendering only on the client
});
// ... use <Chart /> in your component

Analyze Bundles

Use the @next/bundle-analyzer package to visualize your bundle sizes and identify heavy third-party dependencies or accidentally large imports that can be code-split.

Tree Shaking

Modern JavaScript and Next.js ensure unused code is removed, but always be mindful of importing entire libraries when only a small function is needed.