~/TechPurAI
~/tutorials/nextjs-from-scratch/next-image
intermediate·part 11 of 22·2 min read

next/image: automatic image optimization

Updated Aug 16, 2026JavaScript · Next.js

A plain <img> tag ships whatever file is referenced, at whatever size it happens to be, with no help from the framework. next/image's Image component resizes, compresses, and lazy-loads automatically — this part adds a cover image to a post using it.

A local image

text
public/
    images/
        hello-app-router.jpg
tsx
// app/posts/[slug]/page.tsx
import Image from "next/image";

<Image
  src="/images/hello-app-router.jpg"
  alt={post.title}
  width={800}
  height={400}
  priority
/>

width and height aren't optional the way they'd be on a plain <img> — Image uses them to reserve the correct space in the layout before the image itself has loaded, which is what prevents a Cumulative Layout Shift as it comes in. alt is required by the component's own TypeScript types, not just a best practice here — a missing alt is a type error, not just an accessibility oversight. priority tells Next.js to load this specific image eagerly rather than lazily, appropriate for an image that's likely to be a post's Largest Contentful Paint element.

What Image actually does over a plain img tag

Externally hosted images

ts
// next.config.ts
import type { NextConfig } from "next";

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

export default nextConfig;

next/image refuses to optimize an image from a host it doesn't explicitly trust — without this, an Image pointed at https://images.example.com/cover.jpg throws a build-time error rather than silently serving it unoptimized. This is a deliberate safeguard: Next.js's image optimizer fetches and processes whatever URL is given it, and an unrestricted allowlist would let the Image component be pointed at arbitrary third-party URLs.

Filling a container without fixed dimensions

tsx
<div style={{ position: "relative", width: "100%", height: 300 }}>
  <Image src="/images/hello-app-router.jpg" alt={post.title} fill style={{ objectFit: "cover" }} />
</div>

fill is the alternative to fixed width/height — the image stretches to fill its nearest positioned ancestor instead, which needs position: relative (or similar) set explicitly for fill to have a box to actually fill.

Common mistake

Using a plain <img> tag for an image that's genuinely part of the page's content, out of habit or to avoid the remotePatterns configuration step. It works, but skips every optimization above — for a blog with real cover images, that's a meaningful, easily avoidable hit to both load time and Core Web Vitals.

Next: next/link has linked every route in this series so far without ever covering what it actually does beyond rendering an anchor tag — prefetching, specifically.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous10. Structured data: JSON-LD for search and AI answer enginesnext →12. next/link and client-side navigation