Skip to main content
Console Errors

How to Fix Broken Images in HTML

Why images show as broken icons in the browser, how to diagnose the cause, and fixes for wrong paths, CORS issues, and missing files.

Updated

How to Fix Broken Images in HTML

You load your page and instead of an image, you see the broken image icon. The console shows:

GET http://localhost:3000/images/hero.png 404 (Not Found)

A broken image means the browser tried to fetch the file at the URL specified in src and failed. The cause is almost always one of these: the path is wrong, the file doesn’t exist where you think it does, or a server policy is blocking the response.

Diagnosing the problem

Open the Network tab in DevTools and filter by “Img”. Reload the page. Every broken image will show a red status code, usually 404 (file not found) or 403 (forbidden). Click on the failed request to see the full URL the browser requested. Compare that URL to where the file actually lives.

You can also right-click the broken image in the Elements tab and select “Open in new tab” to see exactly what URL the browser is trying to load.

Common causes

1. Wrong relative path

The most frequent cause. Your HTML references src="images/photo.jpg" but the file structure doesn’t match what that relative path resolves to.

Relative paths resolve from the current page’s URL, not from the file’s location on disk. If your page is at /blog/my-post and the image src is images/photo.jpg, the browser requests /blog/images/photo.jpg, not /images/photo.jpg.

<!-- On page /blog/my-post, this resolves to /blog/images/photo.jpg -->
<img src="images/photo.jpg">

<!-- This resolves to /images/photo.jpg regardless of page URL -->
<img src="/images/photo.jpg">

Fix: Use absolute paths (starting with /) for images in the public or static directory. Absolute paths resolve from the site root, so they work the same on every page.

<img src="/images/photo.jpg" alt="A photo">

2. Case sensitivity

Your local macOS or Windows filesystem is case-insensitive. Photo.jpg and photo.jpg resolve to the same file. But Linux servers and most hosting platforms use case-sensitive filesystems. If the file is photo.jpg but your HTML says Photo.jpg, it works locally and breaks in production.

<!-- File on disk: photo.jpg -->
<!-- Works locally on macOS, 404 on Linux server -->
<img src="/images/Photo.jpg">

Fix: Match the case exactly. Adopt a convention (all lowercase is simplest) and stick with it. A CI check that validates asset references against the actual filesystem catches this before deploy.

3. File not in the public directory

Build tools like Vite, Next.js, and Astro distinguish between source files and public/static files. Images in src/ are processed by the bundler and get hashed filenames. Images in public/ are copied as-is and served from the root.

If you put hero.png in src/assets/ and reference it as /hero.png in your HTML, the file doesn’t exist at that path. The bundler never copied it to the output directory.

Fix: For images referenced by URL string in HTML, put them in the public/ directory.

public/
  images/
    hero.png     ← accessible at /images/hero.png
src/
  assets/
    logo.png     ← must be imported in JS/CSS, not referenced by URL

For framework-specific image imports:

// Vite / Astro — import gives you the resolved URL
import heroImg from '../assets/hero.png'

// Use the import in JSX
<img src={heroImg} alt="Hero image" />

4. CORS blocking cross-origin images

When loading images from a different domain, the image itself usually loads fine. But if you’re using the image in a <canvas> (for cropping, filters, or screenshots), the browser enforces CORS. A cross-origin image drawn to canvas taints the canvas, and any attempt to read pixel data throws a security error.

Access to image at 'https://cdn.example.com/photo.jpg' from origin
'http://localhost:3000' has been blocked by CORS policy

Fix: Add the crossorigin attribute to the image tag, and make sure the server sends CORS headers.

<img src="https://cdn.example.com/photo.jpg" crossorigin="anonymous" alt="Photo">

The server must respond with Access-Control-Allow-Origin for this to work. If you don’t control the server, proxy the image through your own backend.

5. Lazy loading with incorrect thresholds

Native lazy loading (loading="lazy") defers image loading until the image is near the viewport. But if the image container has no dimensions (no width/height attributes and no CSS sizing), the browser might calculate that the image is already in view and try to load it, or it might never trigger the load because the container collapses to 0 height.

<!-- Bad — no dimensions, lazy loading behavior is unpredictable -->
<img src="/images/photo.jpg" loading="lazy" alt="Photo">

<!-- Good — dimensions give the browser layout information -->
<img src="/images/photo.jpg" loading="lazy" width="800" height="600" alt="Photo">

Fix: Always set width and height attributes (or use CSS aspect-ratio) on lazy-loaded images. This gives the browser the information it needs to determine when to start loading.

6. Hotlinking blocked by the source server

You link directly to an image hosted on someone else’s server. Their server detects that the request comes from a different domain (via the Referer header) and returns a 403 or a placeholder image.

Fix: Host the image yourself. Download it (with permission) and serve it from your own domain or CDN. Hotlinking is unreliable even when it works, because the source can remove or rename the file at any time.

Adding fallback images

For user-generated content or any source where images might break, add an onerror handler that swaps in a fallback.

<img
  src="/uploads/user-avatar.jpg"
  onerror="this.onerror=null; this.src='/images/default-avatar.png'"
  alt="User avatar"
>

The this.onerror=null prevents an infinite loop if the fallback image also fails.

In a React component:

const ImageWithFallback = ({ src, fallback, alt, ...props }) => {
  const handleError = (e) => {
    e.target.onerror = null
    e.target.src = fallback
  }

  return <img src={src} onError={handleError} alt={alt} {...props} />
}

Prevention

  • Use absolute paths (starting with /) for static assets
  • Adopt lowercase filenames to avoid case sensitivity issues across environments
  • Set width and height on every image to prevent layout shifts and lazy loading issues
  • Check the Network tab after every deploy to catch newly broken images
  • Validate asset references in CI with a link checker that crawls the built site

Hushbug catches broken images, 404 errors, and other resource loading failures automatically while you browse. Coming soon to the Chrome Web Store.