Skip to content
← Journal · Web Design and Development · 4 min

How to Import Images in Next.js from the Public Folder (2026 Guide)

Put the image file anywhere inside your project's `public` folder, then reference it by URL path: a file at `public/images/logo.png` is served at `/images/logo.png`, and that string is what you pass to the `next/image` component. That is the short answer. The rest of this guide covers the two ways to load local images, when each one wins, how Next.js optimizes them for you, and the errors that trip people up in production.

The public folder in 30 seconds

Everything inside `public/` is served as a static file from your site's root URL. `public/favicon.ico` becomes `/favicon.ico`, `public/images/team/anna.jpg` becomes `/images/team/anna.jpg`. Next.js never processes these files at build time, it just serves them. Keep images organized in a subfolder like `public/images/` so your asset URLs stay predictable.

One rule that prevents half the errors in this topic: the URL path never includes the word `public`. The folder name is not part of the route.

Method 1: reference public images by URL path

For images in the public folder, pass the path string to `next/image`. Because Next.js cannot read the file's dimensions at build time from a string, `width`, `height`, and `alt` are required:

components/Logo.tsxtsx
import Image from "next/image";

export default function Logo() {
  return (
    <Image
      src="/images/logo.png"
      alt="Coding Crafts logo"
      width={200}
      height={200}
    />
  );
}

This works identically in the App Router and the Pages Router, in server components and client components. No import statement for the image itself, no configuration.

Method 2: static imports (and when they beat the public folder)

You can also import an image file directly. Next.js then knows its dimensions at build time, so `width` and `height` become optional, and you get automatic blur-up placeholders:

components/Hero.tsxtsx
import Image from "next/image";
import heroShot from "@/public/images/hero.png";

export default function Hero() {
  return (
    <Image
      src={heroShot}
      alt="Product dashboard"
      placeholder="blur"
      priority
    />
  );
}

How to choose:

  • Public folder + string path: images referenced from CMS data or config, images that change without a redeploy, favicons, Open Graph images, anything that needs a stable public URL.
  • Static import: images that are part of your UI (heroes, illustrations, team photos). You get dimension inference, blur placeholders, and a content-hashed URL with immutable caching for free.

Remote images need one config step

Images from a CDN or CMS are not imported at all. Pass the full URL and allowlist the host in your config, otherwise Next.js throws an error:

next.config.tsts
import type { NextConfig } from "next";

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

export default nextConfig;

What next/image actually does for you

If you came to this article from an older tutorial recommending `next-images` or `next-optimized-images`, drop them. Both are deprecated, and the built-in component replaced everything they did:

  • Serves modern formats (AVIF, WebP) to browsers that support them
  • Resizes images per device, so a phone never downloads a 2000px hero
  • Lazy-loads everything below the fold by default
  • Prevents layout shift by reserving space from the width and height

Two props matter more than the rest. Use `priority` on your largest above-the-fold image (your LCP element) so it loads eagerly. And when you use `fill` for responsive images, always pass `sizes` so the browser picks the right resolution:

components/Banner.tsxtsx
import Image from "next/image";

export default function Banner() {
  return (
    <div style={{ position: "relative", aspectRatio: "16 / 5" }}>
      <Image
        src="/images/banner.jpg"
        alt="Conference banner"
        fill
        sizes="(max-width: 768px) 100vw, 1200px"
        style={{ objectFit: "cover" }}
      />
    </div>
  );
}

Troubleshooting: when the image does not show

The path includes /public. `src="/public/images/logo.png"` returns a 404. The correct path is `/images/logo.png`. This is the single most common mistake.

Missing width and height. With a string `src`, omitting dimensions throws: "Image with src ... is missing required width property." Either add them or switch to a static import.

Works locally, 404 in production. Linux servers are case-sensitive: `Logo.png` and `logo.png` are different files in production even though macOS and Windows treat them as the same locally. Match the case exactly.

Remote image throws instead of rendering. The hostname is not in `remotePatterns`. Add it to your config and restart the dev server.

The image is behind a basePath. If your app sets `basePath` in the config, public assets are served under it, and your `src` strings need the prefix too.

Frequently asked questions

Do I need to import images from the public folder?

No. Files in `public` are referenced by URL string, like `/images/logo.png`. Import statements are only for static imports, which is a different (also valid) technique.

Can Next.js optimize images that live in the public folder?

Yes, as long as you render them through the `next/image` component. A plain `<img>` tag serves the original file untouched.

Which is better, the public folder or static imports?

Static imports for images that are part of your interface, because you get dimension inference and blur placeholders. The public folder for images that need stable URLs or come from data.

Wrap-up

Importing images from the public folder in Next.js comes down to one habit: put the file in `public`, reference it by root-relative path, and let the `next/image` component handle optimization. Reach for static imports when the image is part of your UI, add one `remotePatterns` entry for CDN images, and you have covered every case a real project hits. These are the same patterns our Next.js developers ship in production builds like this on-chain trading platform, where image handling directly moves Core Web Vitals.

If you are building on the React ecosystem more broadly, see what changed in React 19, or explore our web development services if you would rather ship with a team that has made these mistakes so you do not have to.

Work with us

Ship Next.js that performs

From Core Web Vitals to production architecture, our senior engineers build Next.js applications that hold up under real traffic.

Talk to Coding CraftsHire Next.js Developers
Abdul Wahab
Written by
Abdul Wahab
Software Engineer at Coding Crafts