What next/image does for you
- Resizes to the widths that are actually needed, and serves AVIF or WebP where they are supported.
- Reserves the space from
widthandheight, so the page never jumps when it loads. - Lazy-loads everything below the fold.
- Caches the resized versions.
import Image from "next/image";
<Image
src="/doors/painted.jpg"
alt="A painted door in a white frame"
width={1200}
height={800}
sizes="(max-width: 768px) 100vw, 50vw"
priority // only for the one above the fold
/>
The two numbers, and the aspect ratio
// intrinsic: the browser knows the shape before the bytes arrive
<Image src="/door.jpg" alt="…" width={1200} height={800} />
// fill: the parent decides the box, and must be positioned
<div style={{ position: "relative", aspectRatio: "3 / 2" }}>
<Image src="/door.jpg" alt="…" fill style={{ objectFit: "cover" }} />
</div>
This is what CLS is
An image with no reserved space pushes the text down when it loads. It is the most common cause of Cumulative Layout Shift, and the reason width and height are required unless you use fill.
sizes, and why it matters more than it looks
sizes drives the srcset selection. Getting it wrong is invisible on your laptop and expensive on someone else’s phone.
sizes="100vw" // full width at every size
sizes="(max-width: 768px) 100vw, 50vw" // full width on a phone, half on a desktop
sizes="(max-width: 768px) 100vw, 33vw" // a three-column grid
// wrong sizes → the browser downloads a 2000px file for a 400px slot
priority, and the largest paint
- One image per page gets
priority: the hero, the one that is the Largest Contentful Paint. - Everything else stays lazy. Marking several removes the benefit.
placeholder="blur"gives a blurred preview: automatic for imported files,blurDataURLfor remote ones.- Remote images need their host listed in
images.remotePatterns— deliberately, so your optimiser cannot be used to resize the whole internet.
Fonts
- The file is downloaded at build time and served from your own domain: no third-party request, no privacy question, one less connection.
- Only the subsets you ask for are included.
- A fallback is generated with matching metrics, so the swap barely moves anything.
- A local file works the same way with
next/font/local.
import { Space_Grotesk } from "next/font/google";
const display = Space_Grotesk({
subsets: ["latin"],
display: "swap",
variable: "--font-display",
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={display.variable}>
<body>{children}</body>
</html>
);
}
display, and the flash
Two font families is a design. Five is a performance problem — each weight and style is another file.
display | Before the font arrives |
|---|---|
swap | Fallback text, swapped when ready — the usual choice |
optional | Fallback, and the web font is skipped if it is slow |
block | Invisible text for up to 3 s — avoid |
fallback | A short block, then the fallback |
And the rest of it
altthat describes the image, oralt=""when it is decorative. Never a filename.- SVG for logos and icons — inline them, and skip the optimiser entirely.
- Do not use
next/imagefor a tiny icon; the overhead is larger than the file. - Check a real page in the Network panel, throttled. The number that matters is the total bytes on a phone.