Optimizing LCP in Next.js
Largest Contentful Paint (LCP) measures loading performance. In this lesson, we'll focus on techniques to optimize LCP in Next.js applications.
Image Optimization with next/image
Images are often the LCP element. Next.js provides an optimized Image component:
import Image from 'next/image'
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero banner showing our main product"
width={1200}
height={600}
priority // Load immediately for LCP
/>
)
}
next/image Benefits
The Image component automatically:
- Serves modern formats (WebP, AVIF) when supported
- Resizes images to match device size
- Lazy loads images below the fold
- Prevents CLS by reserving space
Priority Loading
Mark above-the-fold images as priority to disable lazy loading:
// Hero image - likely LCP element
<Image
src="/hero.jpg"
alt="Welcome banner"
width={1200}
height={600}
priority // Preloads the image
/>
// Below-the-fold - lazy loaded by default
<Image
src="/feature.jpg"
alt="Feature description"
width={400}
height={300}
// No priority = lazy loaded
/>
Responsive Images
Provide multiple sizes for different viewports:
<Image
src="/hero.jpg"
alt="Hero image"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ objectFit: 'cover' }}
/>
Placeholder Blur
Show a blur while the image loads:
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..." // Low-quality placeholder
/>
Font Optimization
Fonts can block rendering and slow LCP.
Using next/font
Next.js provides automatic font optimization:
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Prevents invisible text
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}
Font Display Options
| Value | Behavior |
|---|---|
swap | Show fallback immediately, swap when loaded |
block | Brief invisible text, then show font |
fallback | Very brief invisible, swap if loaded quickly |
optional | Use font only if already cached |
Recommendation: Use swap to prevent invisible text.
Local Fonts
For custom fonts:
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/MyFont.woff2',
display: 'swap',
})
Server Components for Fast Content
Server Components render on the server, sending HTML immediately:
// This is a Server Component by default
export default async function ProductsPage() {
const products = await fetchProducts()
return (
<div>
<h1>Our Products</h1>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
Benefits:
- Content in initial HTML (faster LCP)
- No JavaScript needed for rendering
- Direct database/API access
Reducing Time to First Byte (TTFB)
TTFB affects LCP. To improve it:
Use Caching
// Static generation (fastest)
export default async function Page() {
const data = await getData()
return <div>{data}</div>
}
// With revalidation
export const revalidate = 3600 // Revalidate every hour
// Per-fetch caching
const data = await fetch(url, {
next: { revalidate: 3600 }
})
Edge Runtime
For dynamic pages that need to be fast globally:
export const runtime = 'edge'
export default function Page() {
return <div>Edge-rendered content</div>
}
Avoiding Render-Blocking Resources
Critical CSS
Next.js automatically inlines critical CSS. Avoid adding large external stylesheets in <head>.
Defer Non-Critical JavaScript
import Script from 'next/script'
// Load after page is interactive
<Script
src="https://example.com/analytics.js"
strategy="afterInteractive"
/>
// Load when browser is idle
<Script
src="https://example.com/chat-widget.js"
strategy="lazyOnload"
/>
Code Splitting
Next.js automatically code-splits by route. For additional optimization:
Dynamic Imports
import dynamic from 'next/dynamic'
// Load only when component is rendered
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <div>Loading chart...</div>,
})
Avoid Large Dependencies
// Bad: Import entire library
import _ from 'lodash'
// Good: Import only what you need
import debounce from 'lodash/debounce'
LCP Optimization Checklist
| Item | Check |
|---|---|
| Hero image uses next/image | |
| Hero image has priority prop | |
| Images have explicit dimensions | |
| Fonts use next/font | |
| Font display is 'swap' | |
| Main content is Server Component | |
| Critical CSS is inlined | |
| Non-critical JS is deferred | |
| Heavy components are dynamically imported |
Summary
In this lesson, you learned:
- Optimizing images with next/image
- Priority loading for LCP elements
- Font optimization with next/font
- Server Components for fast initial HTML
- Reducing TTFB with caching
- Avoiding render-blocking resources
- Code splitting strategies
In the next lesson, we'll cover optimizing CLS and INP.

