Images & Fonts in Next.js đŸ–ŧī¸

Introduction 👋

Images and fonts are often the heaviest assets on any web page, making them a critical target for optimization. Next.js ships with built-in Image and next/font primitives that handle resizing, lazy loading, format conversion, and font loading automatically. This tutorial covers both in depth.

Information

These optimizations happen at build time and request time, with no extra configuration required to get meaningful performance gains.

Image Optimization đŸŽ¯

Unoptimized images are one of the most common causes of slow page loads. Next.js automatically resizes, compresses, and serves images in modern formats like WebP, tailored to each user's device.

  • Automatic resizing to the exact dimensions needed.
  • Serving modern formats like WebP or AVIF when supported.
  • Built-in lazy loading for images below the fold.
  • Preventing layout shift by reserving space ahead of time.

The Image Component 🧩

The <Image> component from next/image extends the HTML <img> element with automatic optimization. It requires a width and height (or fill) to prevent layout shift.

components/Hero.tsx

import Image from "next/image";

export default function Hero() {
  return <Image src="/hero.jpg" alt="Team collaborating" width={1200} height={600} />;
}

Local Images 📁

Local images imported directly from your project automatically receive their width and height from the file itself, so you don't need to specify them manually.

components/Logo.tsx

import Image from "next/image";
import logo from "@/public/logo.png";

export default function Logo() {
  return <Image src={logo} alt="Company logo" />;
}

Tip

Importing local images gives you automatic blur placeholders and prevents accidentally using the wrong dimensions.

Remote Images 🌐

For remote images — those hosted on an external domain — you must explicitly provide width and height, and allowlist the domain in your Next.js config.

components/Avatar.tsx

import Image from "next/image";

export default function Avatar({ url }: { url: string }) {
  return <Image src={url} alt="User avatar" width={64} height={64} />;
}

next.config.ts

const nextConfig = {
  images: {
    remotePatterns: [{ protocol: "https", hostname: "images.example.com" }],
  },
};

export default nextConfig;

Important

Remote image domains must be explicitly allowlisted, or the Image component will throw an error at runtime.

Responsive Images 📐

The sizes prop tells the browser how much screen space an image will occupy at different viewport widths, letting Next.js serve an appropriately sized version instead of a one-size-fits-all image.

components/Banner.tsx

import Image from "next/image";

export default function Banner() {
  return (
    <Image
      src="/banner.jpg"
      alt="Promotional banner"
      fill
      sizes="(max-width: 768px) 100vw, 50vw"
    />
  );
}

Image Sizing 📏

Explicit width and height values define the image's aspect ratio, which the browser uses to reserve layout space before the image finishes loading — preventing content from jumping around.

components/ProductPhoto.tsx

<Image src="/product.jpg" alt="Product photo" width={400} height={400} />

Image Quality đŸŽšī¸

The quality prop (1–100) controls the compression level of the optimized image, trading off visual fidelity against file size. The default is 75, which works well for most photos.

components/HeroImage.tsx

<Image src="/hero.jpg" alt="Hero" width={1200} height={600} quality={90} />

Image Formats đŸ—‚ī¸

Next.js automatically negotiates the best supported format per browser — serving AVIF or WebP to modern browsers while falling back to the original format for older ones.

FormatTypical Use
AVIFsmallest file size, newest browsers
WebPbroad modern browser support
Original (JPEG/PNG)fallback for unsupported browsers

Blur Placeholders đŸŒĢī¸

Setting placeholder="blur" shows a low-resolution, blurred version of the image while the full image loads, providing a smoother visual transition instead of a blank space.

components/ArticleImage.tsx

import Image from "next/image";
import photo from "@/public/photo.jpg";

export default function ArticleImage() {
  return <Image src={photo} alt="Article header" placeholder="blur" />;
}

Note

Blur placeholders are generated automatically for local images, but require a manual blurDataURL for remote ones.

Lazy Loading Images 💤

By default, every <Image> is lazy loaded — it only starts fetching once it's about to enter the viewport, saving bandwidth on images the user may never scroll to.

components/Gallery.tsx

<Image src="/gallery-1.jpg" alt="Gallery photo" width={300} height={300} />

Priority Images 🚀

For an image visible above the fold on initial load — like a hero banner — set the priority prop to disable lazy loading and preload it, improving Largest Contentful Paint.

components/HeroBanner.tsx

<Image src="/hero.jpg" alt="Hero banner" width={1200} height={600} priority />

Best Practice

Use priority on your single most important above-the-fold image — overusing it defeats its purpose.

Fill Images đŸ–ŧī¸

The fill prop makes an image expand to fill its parent container, which must be positioned relatively — useful for responsive layouts where exact pixel dimensions aren't known ahead of time.

components/CardImage.tsx

<div style={{ position: "relative", width: "100%", height: 300 }}>
  <Image src="/card.jpg" alt="Card image" fill style={{ objectFit: "cover" }} />
</div>

Image Styling 🎨

Images accept normal className and style props, with objectFit and objectPosition being especially common for controlling how an image crops within its box.

components/Thumbnail.tsx

<Image
  src="/thumb.jpg"
  alt="Thumbnail"
  fill
  style={{ objectFit: "cover", borderRadius: 8 }}
/>

Image Security 🔒

Because remote images must be explicitly allowlisted via remotePatterns, Next.js prevents your app from being used as an open image proxy for arbitrary, untrusted URLs.

Caution

Avoid allowlisting overly broad hostnames like wildcards across an entire top-level domain unless truly necessary.

Image Configuration âš™ī¸

The images key in next.config.ts controls global image behavior — allowed domains, supported formats, device sizes, and cache duration for optimized images.

next.config.ts

const nextConfig = {
  images: {
    formats: ["image/avif", "image/webp"],
    deviceSizes: [640, 750, 828, 1080, 1200],
    minimumCacheTTL: 60,
  },
};

export default nextConfig;

Common Image Patterns đŸ—ēī¸

components/Hero.tsx

<Image src="/hero.jpg" alt="Hero" fill priority style={{ objectFit: "cover" }} />

components/Avatar.tsx

<Image src={user.avatarUrl} alt={user.name} width={40} height={40} style={{ borderRadius: "50%" }} />

components/Gallery.tsx

{photos.map((photo) => (
  <Image key={photo.id} src={photo.url} alt={photo.alt} width={200} height={200} />
))}

Font Optimization 🔤

Fonts can significantly impact loading performance and cause visible layout shift if not handled carefully. The next/font package self-hosts fonts and eliminates extra network requests to third-party font services.

The next/font Package đŸ“Ļ

next/font automatically downloads and self-hosts font files at build time — including Google Fonts — so no requests are sent to external servers at runtime, improving both privacy and performance.

app/layout.tsx

import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

Tip

Self-hosting removes the extra DNS lookup and round trip that traditional Google Fonts <link> tags require.

Google Fonts 🔡

Importing any font from next/font/google automatically downloads it at build time and serves it from your own domain — no <link> tag or external stylesheet needed.

app/layout.tsx

import { Roboto } from "next/font/google";

const roboto = Roboto({
  weight: ["400", "700"],
  subsets: ["latin"],
});

Local Fonts 💾

For custom or licensed fonts, next/font/local optimizes and self-hosts font files that already live in your project, applying the same layout-shift prevention as Google Fonts.

app/layout.tsx

import localFont from "next/font/local";

const myFont = localFont({
  src: "./fonts/CustomFont.woff2",
  display: "swap",
});

Variable Fonts đŸŽ›ī¸

Variable fonts pack multiple weights and styles into a single file, and next/font automatically uses the variable version of a Google Font when one is available, reducing the number of downloaded files.

app/layout.tsx

import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter",
});

Font Display đŸ‘ī¸

The display option controls how a font renders while it's still loading — "swap" shows fallback text immediately and swaps in the custom font once ready, avoiding invisible text.

app/layout.tsx

import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"], display: "swap" });
ValueBehavior
swapshow fallback immediately, swap when ready
blockbriefly hide text, then swap
optionaluse custom font only if it loads very quickly

Font Subsetting âœ‚ī¸

The subsets option tells next/font to include only the character sets your app actually needs — like "latin" — dramatically reducing font file size.

app/layout.tsx

import { Noto_Sans } from "next/font/google";

const notoSans = Noto_Sans({ subsets: ["latin", "cyrillic"] });

Multiple Fonts 🔠

You can load multiple fonts side by side — for example, a heading font and a body font — each assigned to its own CSS variable for use throughout your stylesheets.

app/layout.tsx

import { Inter, Playfair_Display } from "next/font/google";

const inter = Inter({ subsets: ["latin"], variable: "--font-body" });
const playfair = Playfair_Display({ subsets: ["latin"], variable: "--font-heading" });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={`${inter.variable} ${playfair.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Font Performance 🚀

  • Self-hosting via next/font removes external network requests entirely.
  • Font files are automatically included in the preload headers for faster rendering.
  • No layout shift occurs, since fallback font metrics are automatically matched.

Font Best Practices ✅

  • Load fonts once in the root layout rather than per-page to avoid duplication.
  • Only include the subsets your content actually uses.
  • Prefer variable fonts when available to reduce the number of font files.
  • Use display: "swap" unless you have a specific reason not to.

Accessibility Considerations â™ŋ

Every <Image> requires meaningful alt text describing its content for assistive technology users; decorative images should use an empty alt="" instead.

components/DecorativeIcon.tsx

<Image src="/decoration.svg" alt="" width={24} height={24} />

Performance Optimization 🚀

  • Reserve exactly one priority image per page — typically the largest above-the-fold image.
  • Always specify accurate sizes for responsive images to avoid over-fetching.
  • Combine next/font with variable fonts to minimize total font payload.
  • Avoid loading more font weights or styles than your design actually uses.

Common Mistakes âš ī¸

  • Forgetting width/height or fill, causing layout shift or a console warning.
  • Not allowlisting a remote image domain in next.config.ts.
  • Marking every image as priority, which negates its performance benefit.
  • Using a traditional Google Fonts <link> tag instead of next/font/google.

Frequently Asked Questions đŸ’Ŧ

Question

Does the Image component work with SVGs?

Answer

Yes, though SVGs bypass most optimization since they're already vector-based and typically small.

Question

Can I use a font from a source other than Google or a local file?

Answer

Yes — any font files can be self-hosted through next/font/local, regardless of their original source.

Question

Is the Image component required, or can I still use a plain <img>?

Answer

A plain <img> still works, but you lose automatic optimization, lazy loading, and layout-shift prevention.

Summary 📌

Summary

Together, optimized images and fonts are often the single biggest lever for improving real-world page speed — and Next.js handles most of the hard work automatically.