Apr 28, 2026

Next.js Image Optimization Best Practices for App Router Apps

A practical guide to Next.js image optimization best practices covering the next/image component, responsive images, priority loading, remote assets, and Core Web Vitals.

Next.js
Performance
Images
App Router
Core Web Vitals

8 min read

Next.js Image Optimization Best Practices for App Router Apps

If you are searching for Next.js image optimization best practices, you are probably trying to fix one of three problems: a hero image is hurting Largest Contentful Paint, a gallery is shipping oversized assets, or a third-party image source is making the page feel slower than the rest of the app. Images are often the heaviest part of a route, so small implementation choices can create a large difference in perceived speed.

The App Router gives teams a strong server-first architecture, but image performance still depends on how you use next/image, how you size responsive images, and how you decide which assets deserve priority. This guide covers practical next/image responsive images patterns, reliable Next.js image component performance rules, and the review checklist I use before shipping image-heavy pages. For adjacent App Router decisions, pair this with Next.js Client Components Best Practices for App Router Apps, Next.js Server Components Patterns for Faster App Router Apps, and Next.js SEO Checklist for App Router Projects.

Start with the real performance goal

Image optimization is not only about file size. The goal is to deliver the right image, at the right dimensions, at the right time, without shifting layout or blocking critical rendering.

That means every important image needs answers to five questions:

  • Is this image above the fold or below it?
  • Does it need to be indexed and understood by search engines?
  • What dimensions will it occupy on mobile, tablet, and desktop?
  • Is the source local, remote, user-generated, or CMS-managed?
  • Would loading this image early improve or hurt Core Web Vitals?

This framing prevents two common mistakes: adding priority to every large image, or letting every image lazily load even when it is the route's Largest Contentful Paint candidate.

Use the Next.js Image component by default

The built-in Image component is the safest default for application images because it handles resizing, modern formats, lazy loading, and layout stability when configured correctly.

import Image from "next/image";

import profileScreenshot from "@/public/projects/portfolio-dashboard.png";

export function ProjectPreview() {
  return (
    <Image
      src={profileScreenshot}
      alt="Portfolio dashboard showing project cards and performance metrics"
      width={1200}
      height={675}
      className="rounded-lg border border-slate-800"
    />
  );
}

Static imports give Next.js enough information to infer dimensions and generate optimized variants. For many portfolio, blog, and product pages, this is the cleanest Next.js image optimization best practices baseline: import local assets, provide useful alt text, and avoid layout shift by declaring dimensions.

Use a plain <img> only when you have a specific reason, such as rendering a data URI, using an unsupported delivery pipeline, or embedding content where the browser's native behavior is intentionally preferred.

Set sizes for next/image responsive images

The sizes prop is where many image implementations quietly fail. Without it, the browser may choose an image that is much larger than the rendered slot needs.

For a responsive card grid, describe how wide the image will be at different breakpoints:

import Image from "next/image";

type ArticleCardProps = {
  title: string;
  imageUrl: string;
};

export function ArticleCard({ title, imageUrl }: ArticleCardProps) {
  return (
    <article>
      <div className="relative aspect-[16/9] overflow-hidden rounded-md">
        <Image
          src={imageUrl}
          alt=""
          fill
          sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
          className="object-cover"
        />
      </div>
      <h2>{title}</h2>
    </article>
  );
}

This is the heart of next/image responsive images work. The fill prop makes the image occupy the parent, while sizes tells the browser the expected display width. The stable aspect-[16/9] wrapper prevents Cumulative Layout Shift because the layout slot exists before the image finishes loading.

Use empty alt="" only for decorative images. If the image communicates product state, a project result, a diagram, or article context, write alt text that would still make sense if the image did not load.

Treat priority as a precise tool

The priority prop preloads an image and disables lazy loading. It is useful for the image most likely to become the page's LCP element, usually a hero image, product visual, or lead article image.

<Image
  src="/hero/nextjs-dashboard-preview.jpg"
  alt="Next.js dashboard preview with analytics panels"
  width={1600}
  height={900}
  priority
  sizes="100vw"
  className="h-auto w-full object-cover"
/>

Do not add priority to every image above the fold. Preloading too many assets creates competition for bandwidth and can make the actual LCP image slower. Pick one primary visual per route, then verify with Lighthouse, WebPageTest, or the browser Performance panel.

For pages where text is the LCP element, priority images may not help. In that case, focus on keeping below-the-fold images lazy and making sure image dimensions are stable.

Keep Server Components in charge of image data

Image rendering often becomes messy when metadata, CMS data, and client interaction are mixed together. In the App Router, fetch image records in a Server Component when possible, then pass the minimum display data to a small client component only if interaction is required.

// app/projects/page.tsx
import { ProjectGallery } from "@/components/projects/ProjectGallery";
import { getProjects } from "@/lib/projects";

export default async function ProjectsPage() {
  const projects = await getProjects();

  return (
    <ProjectGallery
      projects={projects.map((project) => ({
        id: project.id,
        title: project.title,
        imageUrl: project.coverImage.url,
        imageAlt: project.coverImage.alt,
      }))}
    />
  );
}

This keeps database access, CMS tokens, and transformation logic on the server. It also matches the boundary discipline from Next.js Server Components Patterns for Faster App Router Apps. If a gallery needs filters, lightbox state, or keyboard navigation, keep that interactivity inside a focused Client Component instead of moving the entire data pipeline into the browser.

Configure remote images deliberately

Remote images need an allowlist in next.config.mjs. Keep it narrow. Broad domains make it harder to reason about what the optimizer is allowed to fetch.

// next.config.mjs
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "images.example-cdn.com",
        pathname: "/portfolio/**",
      },
    ],
  },
};

export default nextConfig;

If images come from a CMS, normalize image dimensions and alt text at the content boundary. A good content model stores width, height, source URL, alt text, and optional focal point data. That makes component code simpler and reduces the chance of layout shift.

For user-generated images, validate type and size before upload. Optimization is not a substitute for upload controls. Accepting huge files, SVGs from untrusted users, or arbitrary remote URLs can become a performance and security problem. The request validation mindset in Secure API Route Patterns in Next.js for Safer App Router Backends applies to media pipelines too.

Improve Next.js image component performance with stable layout

Some teams reach for useEffect to measure image containers, calculate dimensions, or swap sources after mount. That often creates hydration drift and extra layout work.

Prefer stable CSS constraints:

<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
  {projects.map((project) => (
    <article key={project.id}>
      <div className="relative aspect-[4/3] overflow-hidden rounded-md">
        <Image
          src={project.imageUrl}
          alt={project.imageAlt}
          fill
          sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
          className="object-cover"
        />
      </div>
    </article>
  ))}
</div>

The grid and aspect ratio define the layout before JavaScript runs. That improves Next.js image component performance because the browser can choose an appropriate source and reserve space without waiting for a client effect. When an effect really is necessary, review React useEffect Best Practices for Next.js Apps so the browser-only behavior stays narrow and predictable.

Align image SEO with page intent

Image SEO is not keyword stuffing. It is about helping search engines and assistive technologies understand what the image contributes to the page.

Use descriptive file names for local assets when practical:

bad: IMG_1029.png
good: nextjs-dashboard-core-web-vitals-report.png

Write alt text for meaning, not decoration:

<Image
  src="/blog/nextjs-dashboard-core-web-vitals-report.png"
  alt="Core Web Vitals report showing improved LCP after Next.js image optimization"
  width={1400}
  height={900}
/>

For articles, the image should reinforce the page topic. If the page targets Next.js image optimization best practices, the lead visual should show the product, workflow, or performance result discussed in the post. That is stronger than a generic abstract graphic and it aligns with the metadata and internal linking guidance in Next.js SEO Checklist for App Router Projects.

Review caching and delivery

The Image component optimizes the response, but the delivery strategy still matters. Local static images are easy because they ship with stable hashed assets. Remote images depend on the upstream server, CDN behavior, and your Next.js deployment environment.

Use this practical checklist:

  1. Keep original source images reasonably sized before committing or uploading.
  2. Use local static imports for stable site assets.
  3. Use a trusted CDN or CMS for remote images.
  4. Configure remotePatterns narrowly.
  5. Provide dimensions or a stable aspect-ratio wrapper.
  6. Add sizes whenever the rendered width changes by viewport.
  7. Use priority only for the likely LCP image.
  8. Test the final route with real viewport widths.

If an image-heavy page also depends on frequently updated CMS content, make the freshness policy explicit. Next.js Caching and Revalidation Guide for App Router Apps explains how route rendering and revalidation choices affect the content users and crawlers receive.

Final takeaway

Good Next.js image optimization best practices are mostly about precision. Use next/image by default, reserve stable layout space, describe responsive widths with sizes, and preload only the image that genuinely affects LCP. Keep image data loading on the server where possible, and move only the interactive gallery behavior into client code.

That combination gives you stronger next/image responsive images, better Next.js image component performance, fewer layout shifts, and pages that remain easier to review as the app grows. Images should make the experience richer without making the route slower, less accessible, or harder for search engines to understand.