Performance details
Did the paragraph jump when the hero loaded? That's CLS. It's preventable.
## Image dimensions for CLS
Every `<img>` and `<video>` needs explicit `width` and `height`. Without them, the browser reserves zero space until the image loads, then content jumps.
Set _intrinsic_ dimensions as HTML attributes:
```html
<img src="/hero.jpg" width="1920" height="1080" alt="Sunset over the city" />
```
Or use `aspect-ratio` in CSS:
```css
.responsive-img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
```
Next.js `<Image>` requires width and height (or `fill`) and handles this automatically.
## `fetchpriority` and `loading`
```html
<!-- Above the fold: load immediately -->
<img
src="/hero.jpg"
width="1920"
height="1080"
alt="..."
fetchpriority="high"
/>
<!-- Below the fold: defer until near viewport -->
<img src="/screenshot.jpg" width="800" height="600" alt="..." loading="lazy" />
```
`fetchpriority="high"`: use for the LCP element only. In Next.js: `<Image priority />`.
`loading="lazy"`: defers until near viewport. **Never on the LCP image.**
## Font loading: preload, preconnect, font-display
### 1. Preconnect to the font CDN
```html
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
```
### 2. Preload critical fonts
```html
<link
rel="preload"
href="/fonts/inter-regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
```
`crossorigin` is required or the browser double-fetches. Preload only fonts used above the fold.
### 3. `font-display: swap`
```css
@font-face {
font-family: "Inter";
src: url("/fonts/inter-regular.woff2") format("woff2");
font-display: swap;
}
```
Tune fallback metrics to reduce the visible shift:
```css
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
```
## Virtualization for large lists
Past ~50 items, render only rows in or near the viewport. 10,000 items, ~30 DOM nodes.
```tsx
import { useVirtualizer } from "@tanstack/react-virtual";
```
For lists that don't justify full virtualization, `content-visibility: auto` skips layout and paint for off-screen blocks:
```css
.list-row {
content-visibility: auto;
contain-intrinsic-size: 1px 64px;
}
```
Virtualization breaks Cmd/Ctrl+F, anchor links, and print views. Plan for these if they matter.
## Common mistakes
- **Removing width and height from images.** Set intrinsic dimensions. Let CSS scale.
- **`loading="lazy"` on the LCP image.** Delays the most important image.
- **`fetchpriority="high"` on every image.** Nothing is high priority if everything is.
- **No `font-display` in `@font-face`.** Three seconds of invisible text.
- **`crossorigin` missing on font preload.** Double-fetch.
- **Preloading every font weight.** A megabyte before the page paints.
- **Virtualizing a list of 20 items.** Complexity for no benefit.