Chapter 10 — Image & Font Optimization
Chapter 10 — Image & Font Optimization
Hey everyone! Two of the biggest, most boring-to-implement-yourself performance wins in web development: correctly sized/lazy-loaded images, and fonts that don't cause layout shift. Next.js bakes both in as near-drop-in components. Let's see what they actually do under the hood — because "just use next/image" without understanding why is how bugs happen.
What we will cover:
- The problems with a plain
<img>tag next/image— what it automates for you- Required props and common mistakes
- The problem with web fonts, and how
next/fontfixes it - Using Google Fonts and local fonts
1. The Problems With Plain <img>
<img src="/hero.jpg" /> Problems this quietly causes: ================================ ✘ Sends the SAME full-size image to phones and 4K monitors alike ✘ No lazy-loading — images below the fold load immediately, wasting bandwidth ✘ No modern format conversion (still shipping .jpg when .webp is smaller) ✘ Browser doesn't know the image's size until it loads → page JUMPS around as images pop in (this is "Cumulative Layout Shift", a real Google ranking factor)
2. next/image — What It Automates
import Image from "next/image";
<Image
src="/hero.jpg"
alt="Hero banner"
width={1200}
height={600}
/>
What happens automatically behind this ONE component: ========================================================= ✔ Resizes the image to fit the ACTUAL rendered size on each device ✔ Serves modern formats (WebP/AVIF) when the browser supports them ✔ Lazy-loads by default — images off-screen don't load until you scroll near them ✔ Reserves the exact space (width/height) BEFORE the image loads → zero layout shift, page doesn't jump around ✔ Generates a blurred placeholder automatically, if you ask for it
3. Required Props & Common Mistakes
❌ MISTAKE 1 — forgetting width/height:
==========================================
<Image src="/hero.jpg" alt="Hero" />
// Error! next/image REQUIRES width+height (or "fill") so it can
// reserve space and prevent layout shift. This isn't optional.
✅ FIX — either give explicit dimensions, or use "fill" for a
container-sized image:
<div style={{ position: "relative", width: "100%", height: "400px" }}>
<Image src="/hero.jpg" alt="Hero" fill style={{ objectFit: "cover" }} />
</div>
❌ MISTAKE 2 — using next/image with a REMOTE (external) image URL
without configuring it:
==========================================================================
<Image src="https://cdn.example.com/photo.jpg" ... />
// Error! Next.js blocks unknown external hosts by default (security).
✅ FIX — allow the domain in next.config.js:
// next.config.js
module.exports = {
images: {
remotePatterns: [{ hostname: "cdn.example.com" }],
},
};
4. Priority Loading for Above-the-Fold Images
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
Without "priority": your hero/banner image is lazy-loaded like
everything else — meaning it might load LATE, hurting your
"Largest Contentful Paint" score (a real performance metric).
With "priority": Next.js preloads it immediately, no lazy-loading delay.
Use this ONLY for the image visible without scrolling — using it
everywhere defeats the whole purpose of lazy-loading.
5. The Problem With Web Fonts
Traditional web font loading (Google Fonts <link> tag):
============================================================
1. Browser downloads your HTML/CSS
2. Browser requests the font from fonts.googleapis.com (a THIRD PARTY!)
3. Text renders with a FALLBACK font while waiting
4. Font arrives → text suddenly RESHUFFLES into the real font
("Flash of Unstyled Text" / layout shift, again)
Also: that third-party request is a real privacy/performance cost —
you're making every visitor's browser talk to Google's servers.
6. next/font — Self-Hosted, Zero Layout Shift
// app/layout.js
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
What next/font does differently: ==================================== ✔ DOWNLOADS the font file at BUILD time and self-hosts it with your app → no request to Google's servers at all, at runtime ✔ Automatically calculates fallback font metrics to MATCH the real font's size → no visible reshuffle when the real font loads ✔ Works identically for Google Fonts AND your own local font files
Local (self-owned) fonts:
============================
import localFont from "next/font/local";
const myFont = localFont({ src: "./my-custom-font.woff2" });
Key Points to Remember
- Plain
<img>ships oversized images and causes layout shift —next/imagefixes both automatically next/imagerequireswidth+height(orfill) — this is what enables zero layout shift, not a limitation to work around- Remote image domains must be allow-listed in
next.config.js— a deliberate security default - Use
priorityonly on the one image visible without scrolling (your LCP element) next/fontself-hosts fonts at build time — no third-party request, no flash of unstyled text- Both tools exist because "just use a normal tag" quietly costs you real Core Web Vitals score
What's Next?
We've now covered the core surface area of Next.js. The next two chapters go deeper under the hood — how rendering and caching ACTUALLY work internally, past the "just trust the framework" level.
Keep coding, keep learning! See you in the next one!
Post a Comment