React Development

Master React Image Paths: The Ultimate Local Fix (2025)

Tired of broken images in React? Our 2025 guide masters local image paths. Learn the difference between the public and src folders and fix image issues for good.

A

Alex Miller

A senior front-end developer specializing in React and modern web performance optimization.

7 min read8 views

Introduction: The Broken Image Conundrum

You’ve just spun up a new React project with Create React App or Vite. You've structured your files neatly, placing your images in a dedicated `assets/images` folder. You write the familiar HTML: <img src="./assets/images/logo.png">. You save, the browser reloads, and... you're greeted by the dreaded broken image icon. It's a rite of passage for every React developer, a frustrating puzzle that seems simple but hides a crucial concept about how modern JavaScript frameworks work.

Fear not. This isn't a bug in your code or a flaw in React. It's a misunderstanding of how React's build process handles assets. In this comprehensive 2025 guide, we'll demystify React image paths once and for all. We'll explore the two primary methods for handling local images, compare their pros and cons, and provide you with a clear mental model to ensure your images load perfectly, every time.

Why Do React Image Paths Break? The Build Process Explained

In a simple HTML/CSS website, the browser directly requests files from the server based on the paths you provide. The file structure on your computer directly maps to the file structure on the web server. React applications, however, are more complex. They have a build step.

Tools like Webpack (used by Create React App) or Vite bundle your application. They parse all your JavaScript files, starting from `index.js`, and package them, along with other assets like CSS and images, into optimized static files for production. When you reference an image with a relative path like "./images/logo.png" inside a component, React doesn't know what to do with it by default. The path is relative to your source file, but that file structure doesn't exist in the final `build` output.

The bundler needs to be explicitly told about the image so it can:

  • Copy the image to the final build directory.
  • Potentially optimize it (compress, resize).
  • Generate a unique, cache-busted filename (e.g., `logo.a8f5f6.png`).
  • Provide your component with the correct final path to that processed image.

This is where the two main approaches to handling images diverge: placing them inside or outside the bundler's reach.

The Two Primary Solutions for Local Images

React offers two distinct locations for your static assets, each with its own workflow and trade-offs: the `public` folder and the `src` folder.

Method 1: The `public` Folder (The Escape Hatch)

The `public` folder is a special directory that acts as an "escape hatch" from the build process. Any file placed in the `public` folder is not processed by the bundler. Instead, it's copied directly into the root of your final `build` directory, maintaining its exact filename and relative path.

How to use it:

  1. Place your image (e.g., `logo.png`) inside the `public` folder. You can create subdirectories, like `public/images/logo.png`.
  2. In your JSX, reference it with an absolute path from the root. Create React App provides a special environment variable, process.env.PUBLIC_URL, to help with this.
// Structure:
// public/
//   images/
//     logo.png
// src/
//   components/
//     Header.js

// In src/components/Header.js
function Header() {
  return (
    <img 
      src={`${process.env.PUBLIC_URL}/images/logo.png`} 
      alt="Company Logo" 
    />
  );
}

// Note: In newer versions or other frameworks like Vite, you might just use a root-relative path:
// <img src="/images/logo.png" alt="Company Logo" />

When to use the `public` folder:

  • You need a file with a specific, predictable name (e.g., `robots.txt`, `favicon.ico`).
  • You have thousands of images and don't want to `import` them all, accepting the performance trade-off.
  • You are integrating with a third-party script that expects assets at a specific URL.

Method 2: The `src` Folder (The Bundler's Way)

This is the generally recommended approach for most assets in a React application. By placing your images inside the `src` directory (e.g., `src/assets/images`), you are telling the bundler to treat them as part of your source code.

How to use it:

You use a JavaScript `import` statement to get the final path of the image. The bundler processes the image and the `import` statement returns a string containing the final URL.

// Structure:
// src/
//   assets/
//     images/
//       logo.png
//   components/
//     Header.js

// In src/components/Header.js
import logo from '../../assets/images/logo.png'; // Path is relative to the JS file

function Header() {
  return <img src={logo} alt="Company Logo" />;
}

When the bundler sees `import logo from ...`, it does its magic: it copies the image to the build folder, gives it a unique hash in the filename (like `logo.5d8b8e.png`), and the `logo` variable in your code becomes the string `"/static/media/logo.5d8b8e.png"`. This gives you several key advantages:

  • Cache Busting: If you change the image, its hash changes, and so does the filename. This forces browsers to download the new version instead of serving a stale, cached one.
  • Missing File Errors: If you misspell the path or the file is missing, the `import` will fail during the build process (or even in your IDE), not silently in the browser.
  • Optimization: The bundler's configuration can be extended with plugins to automatically compress and optimize images during the build.

Comparison: `public` vs. `src` Folder Method

Side-by-Side: `public` vs. `src` Image Handling
Feature`public` Folder Method`src` Folder (Import) Method
File HandlingCopied as-is to the build root. Bypasses the bundler.Processed and bundled as a module.
Path in CodeAbsolute path from the root (e.g., `/images/logo.png`).Import the asset and use the resulting variable.
PerformanceNo filename hashing for cache busting. No automatic optimization.Automatic cache busting via filename hashing. Can be optimized by bundler plugins.
Error CheckingResults in a 404 error in the browser if path is wrong. No build-time check.Causes a compile-time error if the file is missing. Safer.
Dynamic PathsEasier. Can construct a string path like `/images/user-${id}.png`.More complex. Requires `require()` or pre-importing all possible images.
Best Use CaseStatic assets that must not be renamed (e.g., `favicon.ico`, `robots.txt`).Component-specific images, logos, icons, and any asset that benefits from optimization and cache busting.

Handling Dynamic Image Paths: A Common Pitfall

A frequent challenge arises when you need to render an image based on a prop or state. A developer's first instinct might be to try this:

// THIS WILL NOT WORK
function UserAvatar({ imageName }) {
  // The bundler can't resolve this dynamic import path at build time
  const imagePath = `../assets/images/${imageName}`;
  return <img src={imagePath} alt="User" />;
}

This fails because the bundler needs to know exactly which files to include at build time. It cannot resolve a dynamic string that will only be known at run time. The `import` method we saw earlier also only works with static string paths.

The Solution: `require()` or an Image Map

For CommonJS environments (like older Create React App versions), you can use `require()` directly in the `src` attribute. The bundler is smart enough to understand this pattern and will bundle all possible images from that directory.

// Solution 1: Using require()
function UserAvatar({ imageName }) {
  // Webpack will see this and bundle everything in the 'images' folder
  const imageSrc = require(`../assets/images/${imageName}`).default;
  return <img src={imageSrc} alt="User" />;
}

A more modern and performant approach, especially with ES Modules and Vite, is to create an "image map" by pre-importing all the images you might need and selecting the correct one at runtime.

// Solution 2: The Image Map
import user1 from '../assets/avatars/user1.png';
import user2 from '../assets/avatars/user2.png';
import userDefault from '../assets/avatars/default.png';

const avatarMap = {
  user1,
  user2,
  default: userDefault,
};

function UserAvatar({ avatarKey = 'default' }) {
  const imageSrc = avatarMap[avatarKey] || avatarMap.default;
  return <img src={imageSrc} alt="User avatar" />;
}

This second method is more explicit and gives you finer control, preventing the bundler from including unused images from a large directory.

Best Practices for React Image Handling in 2025

  • Default to the `src` folder: For any image that is part of your component UI (logos, icons, banners), always use the `import` method. The benefits of cache-busting and build-time error checking are invaluable.
  • Use `public` sparingly: Reserve the `public` folder for truly static assets that must not be processed, like `manifest.json` or brand assets for a press kit download.
  • Co-locate images with components: For images that are only used by a single component, consider placing them in the same folder (e.g., `src/components/UserProfile/avatar.png`). This makes your components more self-contained and portable.
  • Embrace SVGs as Components: For icons, instead of using `<img>`, you can import SVGs directly as React components. This offers superior scalability and allows you to control properties like color and size with CSS or props.
    // With Create React App
    import { ReactComponent as Logo } from './logo.svg';
    
    function App() {
      return <Logo className="app-logo" />;
    }
  • Optimize Your Images: Before even adding images to your project, run them through an optimization tool like Squoosh or ImageOptim to reduce their file size without sacrificing quality.

Conclusion: Path to Mastery

The confusion around React image paths stems from the powerful but invisible work of the module bundler. By understanding the fundamental difference between the `public` and `src` folders, you move from guesswork to intentional design. The `public` folder is a simple passthrough, while the `src` folder integrates assets into the build process, offering optimization, error-checking, and cache-busting.

By defaulting to the `src` import method for your UI images and reserving the `public` folder for specific exceptions, you'll build more robust, performant, and maintainable React applications. The broken image icon will no longer be a source of frustration, but a reminder of the sophisticated tooling that makes modern web development so powerful.