Your Next.js site takes 6 seconds to load on mobile, and images weigh more than the text. Visitors leave before they even see what you sell. This is the concrete problem we tackle today: optimizing images and fonts in Next.js isn't a cosmetic detail, it's a direct lever on conversions. Every second of loading delay can cost you up to 7% of lost sales. We, at Meteora Web, see this every day in projects that come to us: beautiful but slow sites, with 2 MB images and fonts that block rendering. In this guide, we show you how to fix it, starting with the why before the how.
Why are images and fonts the first enemies of your Core Web Vitals?
Google's Core Web Vitals measure three things: LCP (Largest Contentful Paint), how long the largest element on the page takes to appear; CLS (Cumulative Layout Shift), how much the page "jumps" while loading; and INP (Interaction to Next Paint), how responsive it is to clicks. Images directly impact LCP and CLS. Fonts, if loaded poorly, block text rendering and worsen LCP and INP. An e-commerce client had images weighing several MB: by optimizing them, we reduced weight by 60% without quality loss. The result? LCP went from 4.5 seconds to 1.8 seconds and conversions increased by 12%. The point is that speed isn't a luxury, it's a requirement.
Sponsored Protocol
How does Next.js use the Image component to optimize images?
The next/image component isn't a simple <img> tag. It's a complete system that includes automatic lazy loading, modern formats like WebP and AVIF, and responsive sizing. But the real strength is layout shift prevention: if you declare width and height, Next.js reserves the necessary space and CLS stays at zero. Here's a practical example:
import Image from 'next/image';
export default function ProductCard({ product }) {
return (
<div className="card">
<Image
src={product.image}
alt={product.name}
width={600}
height={400}
sizes="(max-width: 768px) 100vw, 50vw"
priority={false}
className="rounded-lg"
/>
</div>
);
}
The sizes prop tells the browser how much space the image will occupy based on the viewport. Without it, Next.js downloads images that are too large for small screens. Use priority only for the LCP image (usually the one above the fold); otherwise, default lazy loading is the right choice.
Which formats should you choose for images in Next.js?
Next.js automatically converts images to WebP or AVIF if the browser supports them. We recommend always starting with lightweight formats like WebP and using AVIF for photographic images where quality is a priority. If you have PNG images with transparency, WebP handles them perfectly. The operational advice: never upload a 2 MB JPEG. Before importing it, compress it with tools like Squoosh or ImageOptim. An optimized image weighs between 50 and 150 KB, not more.
Sponsored Protocol
How to configure next.config.js for advanced optimization?
Next.js's default configuration is good, but you can push further. The next.config.js file lets you set deviceSizes and imageSizes to generate exactly the dimensions you need. Here's our base configuration:
// next.config.js
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
},
};
minimumCacheTTL is crucial: it tells Next.js to cache optimized images for 30 days. This reduces server load and speeds up subsequent visits. If images change frequently, lower the TTL; if they're static, raise it. We use this configuration for almost all clients, and it works.
How to optimize fonts in Next.js without blocking rendering?
Fonts are a silent problem. If you load a font with traditional @font-face, the browser blocks text rendering until it downloads. This worsens LCP and INP. Next.js solves this with the next/font system, which loads fonts automatically with font-display: swap and serves them from the same domain, eliminating extra requests. Here's how to use it:
Sponsored Protocol
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}
With next/font, you don't worry about preload or display: swap: Next.js does it for you. Additionally, fonts are automatically self-hosted, which improves privacy and speed because there are no requests to Google Fonts. If you have a custom font, use localFont to load it from your server.
How to avoid layout shift with fonts?
CLS caused by fonts happens when text changes size after loading. With next/font, Next.js generates an @font-face with automatic size-adjust, minimizing the jump. But there's an extra trick: always declare font-family with a similar fallback, like system-ui or Arial. This way, if the font is delayed, text appears with the fallback, and layout doesn't jump. We always test with Lighthouse and PageSpeed Insights: if CLS is above 0.1, there's a problem.
Sponsored Protocol
How to measure Core Web Vitals after optimization?
Optimizing without measuring is like driving in the dark. Use PageSpeed Insights for an overview and Lighthouse in DevTools for local debugging. But the most important data comes from Google Search Console, in the "Experience" → "Core Web Vitals" section. There you see real user data, not simulations. For continuous monitoring, we use @next/bundle-analyzer to understand what weighs in the bundle and web-vitals to track metrics in production. Here's an example report:
// app/layout.jsx
import { reportWebVitals } from 'next/web-vitals';
export function reportWebVitals(metric) {
if (metric.label === 'web-vital') {
console.log(metric.name, metric.value);
}
}
This code shows LCP, CLS, and INP values in the console. If LCP is under 2.5 seconds, CLS under 0.1, and INP under 200ms, you're on the right track. If not, go back and check images and fonts.
What common mistakes should you avoid in image and font optimization?
The first mistake is not using next/image and using classic <img> tags. You lose all automatic optimizations. The second is loading decorative images with priority: this forces immediate loading and slows down the page. The third is using too many fonts or weights: each additional font is an extra request. We recommend a maximum of two font families with two weights each. The fourth mistake is ignoring caching: without minimumCacheTTL, the server regenerates images on every request. Finally, not testing on real mobile: desktop simulations don't show mobile network issues. Always use Lighthouse throttling to simulate a 4G connection.
Sponsored Protocol
What to do now
You don't need to rebuild everything. Here are three immediate actions you can take today:
1. Replace all <img> tags with next/image and declare width and height. It's the most impactful change for CLS.
2. Migrate fonts to next/font and remove the Google Fonts <link>. You'll gain speed and privacy.
3. Run PageSpeed Insights before and after and compare numbers. If LCP drops below 2.5 seconds, you've hit the mark.
If you want to dive deeper into building a complete Next.js site that truly performs, start with our pillar guide on Next.js App Router. And if you manage real estate listings, check out how to optimize property cards to sell more. We, at Meteora Web, always think in terms of return: a fast site isn't a cost, it's an investment that pays off.