Home/Learn/Next.js/Images and Media

Images and Media

Intermediate
UX & Assets

next/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.

priority on the hero; sizes with fill
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.

remotePatterns, formats, and who pays for transforms
// 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.

No next/video; next/font self-hosts
// 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 Aria
1

What does next/image do for you automatically?

Easy
2

Why is the priority prop important on a hero image?

Medium
3

Why must remote image hosts be listed in next.config?

Medium

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.

Loading discussion…