Images and Media
Intermediatenext/image resizes, converts to modern formats, lazy-loads and reserves space in one component. The defaults are right; the mistakes are priority and remote patterns.
Overview
Images are usually the largest thing on a page and the main cause of layout shift, and `next/image` addresses both by default: it serves an appropriately sized AVIF or WebP, reserves the space before the file lands, and lazy-loads anything below the fold. That last default is also the most common mistake, because lazy-loading the hero delays the exact element the Largest Contentful Paint measures. The rest is configuration — telling Next which remote hosts are allowed, and what a fill layout needs from its container.
The Component
Fixed dimensions, fill, and the priority flag that matters.
import Image from 'next/image'
// A local import gives Next the dimensions automatically
import cover from '@/public/cover.png'
<Image src={cover} alt="" placeholder="blur" />
// A remote image needs explicit dimensions — they reserve the space
<Image src={problem.coverUrl} alt="" width={640} height={360} />
// Unknown dimensions: fill, with a positioned, sized parent
<div className="relative aspect-video">
<Image src={url} alt="" fill className="object-cover" />
</div>
// fill without a positioned parent silently produces a broken layout.
// The hero — the LCP element. Never lazy, always priority:
<Image src={hero} alt="" priority sizes="100vw" />
// priority preloads it and disables lazy loading. Missing it on the
// LCP image is the single most common next/image mistake, and Next
// warns about it in development.
// sizes tells the browser how wide the image will RENDER, so it can
// pick the right file. Without it, a full-width file is downloaded
// for a thumbnail:
<Image src={url} alt="" fill sizes="(max-width: 768px) 100vw, 33vw" />
// alt is required — an empty string for decorative images.Configuration and Cost
Remote hosts, formats, and the bill on Vercel.
// next.config.js — remote hosts must be allow-listed, or the image
// simply does not render
module.exports = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: '**.supabase.co', pathname: '/storage/**' },
{ protocol: 'https', hostname: 'images.unsplash.com' },
],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
minimumCacheTTL: 60 * 60 * 24 * 30,
},
}
// The allow-list is a security control: without it, your optimizer
// would resize arbitrary URLs on request — someone else's bandwidth
// bill, paid by you.
// Optimisation is not free. On Vercel it is billed per source image
// transformed. Two ways to control it:
// - already-optimised assets from a CDN: unoptimized
<Image src={cdnUrl} alt="" width={40} height={40} unoptimized />
// - a custom loader pointing at Cloudinary, imgix or Supabase's own
// transformer
images: { loader: 'custom', loaderFile: './lib/image-loader.ts' }
// Static export has no optimizer at all — unoptimized: true is
// required there.Video and Other Media
What Next does not optimise, and how to keep it cheap.
// There is no next/video. Serve video from a real video host —
// Mux, Cloudflare Stream, Supabase — not from /public. A 50MB MP4
// in /public is committed to git, bundled into the deployment and
// served without adaptive bitrate.
<video controls preload="metadata" poster="/thumb.jpg" playsInline>
<source src={hlsUrl} type="application/x-mpegURL" />
</video>
// preload="metadata" fetches only the duration. preload="auto" on a
// list of videos downloads all of them.
// A YouTube embed loads roughly a megabyte of player before anyone
// presses play. Render a thumbnail and swap in the iframe on click —
// which is exactly what this platform's video panel does.
// Fonts, briefly (they belong with images as render-blocking assets):
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'], display: 'swap', variable: '--font-sans' })
<html className={inter.variable}>
// next/font self-hosts the file at build time, so there is no request
// to Google at runtime and no layout shift when it arrives.Key Points to Remember
- 1next/image reserves space, serves modern formats and lazy-loads below the fold by default
- 2The hero image needs priority — lazy-loading the LCP element is the most common mistake with this component
- 3fill requires a positioned, sized parent, and sizes tells the browser the rendered width
- 4Remote hosts must be allow-listed in remotePatterns, which is a security control as well as configuration
- 5Image optimisation is billed per transform on Vercel; there is no next/video, so use a real video host
Interview Questions
Sign in to ask AriaWhat does next/image do for you automatically?
Why is the priority prop important on a hero image?
Why must remote image hosts be listed in next.config?
Ask Aria about Images and Media
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.