Skip to content
NewHost
Menu

Next.js Image Optimisation Self-Hosted - sharp, Caching and sizes

Next.js image optimisation when you self-host - how sharp and the image cache work, remotePatterns, formats, the sizes prop and keeping RAM use under control.

By NewHost team · · 6 min read

When you self-host Next.js, next/image optimisation runs on your own server: requests to /_next/image are resized and converted by sharp, then cached on disk in .next/cache/images. It works out of the box with next start and standalone output, but it uses CPU and memory on your server, so the config choices you make (formats, sizes, cache lifetime, allowed sources) matter far more than on a platform that bills image optimisation separately. This guide explains how it works and how to tune it.

How self-hosted image optimisation works

When the browser requests an image rendered by next/image, it asks for a URL like:

/_next/image?url=%2Fhero.jpg&w=1080&q=75

The Next.js server then:

  1. Checks that the source is allowed (local file, or a remote URL matching your remotePatterns).
  2. Looks for a cached version in .next/cache/images.
  3. If there is none, or it has expired, fetches the original, resizes it to the requested width with sharp, converts it to the best format the browser accepts (AVIF or WebP if enabled), and saves it to the cache.
  4. Returns it with cache headers.

The first request for each size and format is the expensive one. After that, it is served from disk.

sharp is included

Since Next.js 15, sharp is used automatically for next start and standalone output. You do not need to npm install sharp any more. If you are on an older version and see a warning about sharp in production, upgrading Next.js is the cleanest fix.

A sensible production config

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

const nextConfig: NextConfig = {
  images: {
    formats: ["image/avif", "image/webp"],
    remotePatterns: [
      new URL("https://cdn.example.co.za/uploads/**"),
      { protocol: "https", hostname: "images.example-cms.com", pathname: "/my-space/**" },
    ],
    minimumCacheTTL: 2678400, // 31 days, in seconds
    qualities: [60, 75],
  },
};

export default nextConfig;

What each setting does:

Option What it controls Notes
formats Output formats, in order of preference Default is WebP only. AVIF files are smaller but slower to encode, so the first request takes longer and uses more CPU
remotePatterns Which external images may be optimised Be specific; a wildcard hostname lets anyone use your server to resize arbitrary images
minimumCacheTTL Minimum seconds an optimised image is cached The default in Next.js 16 is 4 hours; raise it if your images rarely change
qualities Allowed quality values In Next.js 16 the default is [75] only; other values must be listed
deviceSizes / imageSizes Widths Next.js may generate Fewer widths means fewer variants to create and cache

The full list is in the image component docs. The older images.domains option is deprecated; use remotePatterns.

Next.js 16 also blocks optimising images from local and private IP addresses by default. If you genuinely need that (an internal media server, for example), there is an explicit dangerouslyAllowLocalIP option, but think twice before enabling it.

Get the sizes prop right

The single biggest win is usually the sizes prop. Without it, a responsive image (fill, or CSS that makes it fluid) assumes it fills the full viewport width, so phones download larger images than they need.

import Image from "next/image";

export function ProductCard({ src, name }: { src: string; name: string }) {
  return (
    <div style={{ position: "relative", aspectRatio: "4 / 3" }}>
      <Image
        src={src}
        alt={name}
        fill
        sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
        style={{ objectFit: "cover" }}
      />
    </div>
  );
}

Read sizes as: "on small screens this image is full width, on medium screens half, otherwise a third". The browser then picks the smallest generated width that fits.

For the main image above the fold (your Largest Contentful Paint element), tell Next.js to load it early. In Next.js 16, use the preload prop; in Next.js 15 and earlier, the equivalent is priority. Use it on one or two images per page, not all of them.

Keep CPU and RAM under control

On a server with fixed memory, image optimisation is often the biggest source of spikes. To keep it calm:

  • Upload sensible originals. A photo straight off a phone camera is far larger than any screen needs. Resize originals to a reasonable maximum width before they go into public or your CMS.
  • Limit widths. Trim deviceSizes and imageSizes to the widths your layout actually uses.
  • Limit qualities. Each extra quality value multiplies the variants.
  • Think about AVIF. It saves bandwidth, but encoding costs more CPU. On a small plan, WebP-only is a reasonable choice.
  • Pre-warm important pages after a deploy by visiting them, so the first real visitor does not wait for the encode.
  • Use static imports for local images (import hero from "./hero.jpg"). Next.js then knows the dimensions (no layout shift) and can generate a blur placeholder with placeholder="blur".

When to skip built-in optimisation

The built-in optimiser is a good default, but there are alternatives:

  • An image CDN or your CMS's image API. Many headless CMSs resize images on their side. Write a custom loader that builds their URL, and your server does no image work at all.
  • unoptimized. For SVGs, tiny icons or images you already optimise in your build pipeline, set unoptimized on the component (or globally in config) and the file is served as-is.

The cache and deployments

The image cache lives in .next/cache/images on the server's disk. On a fresh deploy the cache may start empty, so the first requests after a deploy regenerate images. That is normal. If you run several instances, each has its own cache unless they share storage, so put a CDN in front if image traffic is heavy.

Browsers and CDNs also cache the response. Remember that caching is keyed on the URL: if you replace an image in public but keep the same file name, visitors may see the old version until caches expire. Changing the file name (for example hero-v2.jpg) avoids that.

On NewHost

Next.js apps on NewHost run as a normal Node.js server, so next/image optimisation works without extra setup. If your site is image-heavy, a plan with more RAM per app gives sharp more room: Starter has 512 MB, Developer 1 GB, Business 2 GB and Agency 4 GB per app. Compare them on the pricing page.

Frequently asked questions

Does next/image work when self-hosting?

Yes. Next.js optimises images on your own server using sharp and caches the results on disk. No external service is needed.

Where does Next.js cache optimised images?

In .next/cache/images on the server. Entries are kept for at least minimumCacheTTL seconds, or longer if the upstream image sends a longer cache header.

Why is image optimisation using so much memory?

Resizing and encoding large images, especially to AVIF, is memory- and CPU-intensive. Resize originals before upload, reduce the number of widths and qualities, and consider WebP-only on small servers.

Why do I get a 400 error for remote images?

The image host or path does not match your remotePatterns. Add a pattern for that hostname and path, then rebuild and redeploy.

For more on running Next.js on your own server, read self-hosting Next.js vs Vercel and ISR and caching when self-hosted, or see NewHost Next.js hosting.

Related guides

Ready to launch on NewHost?

Choose a plan and go live today, or tell us what you need and we'll recommend the right setup.