Skip to content

Images and Fonts

next/image resizes, converts to modern formats, and lazy-loads your images while reserving their space, and next/font self-hosts your fonts at build time — so the page loads fast and never shifts.

next/image resizes, converts, and lazy-loads

Section titled “next/image resizes, converts, and lazy-loads”

Image is a drop-in replacement for <img> that optimizes on demand. It generates a resized version per device, serves modern formats like WebP or AVIF when the browser supports them, and lazy-loads anything below the fold automatically.

When you import a local file, Next.js reads its intrinsic dimensions at build time, so width and height are filled in for you.

app/page.tsx
import Image from 'next/image'
import hero from './hero.png'
export default function Page() {
return <Image src={hero} alt="Product hero shot" />
}

For any image whose size Next.js cannot infer — a remote URL, for example — you MUST pass width and height. They reserve the exact box before the pixels arrive, which is what prevents Cumulative Layout Shift. Add priority to the one image that is your Largest Contentful Paint so Next.js preloads it and skips lazy-loading.

// Remote image: width + height are required to reserve space
import Image from 'next/image'
export default function Avatar() {
return (
<Image
src="https://cdn.example.com/avatar.png"
alt="User avatar"
width={64}
height={64}
priority // preload the LCP image, skip lazy-loading
/>
)
}

When the dimensions are genuinely unknown, use fill inside a positioned parent and let CSS size the box instead.

// Unknown dimensions: use fill inside a positioned parent
<div style={{ position: 'relative', width: 400, height: 300 }}>
<Image src={src} alt="" fill sizes="400px" style={{ objectFit: 'cover' }} />
</div>

Optimizing an arbitrary external URL would be an open proxy, so Next.js refuses unless you allowlist the host. Declare images.remotePatterns in next.config and be as specific as you can.

next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.example.com', pathname: '/**' },
],
},
}
export default nextConfig

next/font downloads the font files at build time and serves them from your own origin. There is no runtime request to Google, and because the font metrics are known ahead of time it reserves the right space and produces zero layout shift.

// app/layout.tsx — self-hosted, zero layout shift, no request to Google
import { Inter } from 'next/font/google'
import localFont from 'next/font/local'
const inter = Inter({ subsets: ['latin'], display: 'swap' })
const brand = localFont({ src: './brand.woff2' })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.className}>
<body className={brand.className}>{children}</body>
</html>
)
}
flowchart LR
  Src[Source asset] --> Build[Build step]
  Build --> Opt["Resized, WebP/AVIF, self-hosted font"]
  Opt --> Reserve["width + height reserve space"]
  Reserve --> Page["No layout shift, fast LCP"]
From source asset to a stable, fast page
Why must you pass width and height to a remote next/image?
What does the priority prop do?
What is required before next/image will optimize a remote URL?
What does next/font do at build time?