Why React Images Load Locally but Not on AWS Amplify: Troubleshooting Guide

As a React developer, few things are more frustrating than deploying your app to AWS Amplify—only to find that images that loaded perfectly locally are now broken in production. You’ve triple-checked the code, verified file paths, and confirmed the images exist… so why do they work on your machine but not on Amplify?

The root cause often lies in differences between your local development environment and how AWS Amplify builds, serves, and caches assets in production. This guide will demystify these discrepancies, walk you through common culprits, and provide step-by-step solutions to get your images loading reliably on Amplify.

Table of Contents#

  1. Understanding the Local vs. Amplify Environment
  2. Common Causes of Missing Images on Amplify
  3. Step-by-Step Troubleshooting Guide
  4. Prevention Tips for Future Deployments
  5. Conclusion
  6. References

1. Understanding the Local vs. Amplify Environment#

To troubleshoot, it’s critical to first grasp why images behave differently locally vs. on Amplify:

  • Local Development: When you run npm start, React uses a development server (e.g., webpack-dev-server for Create React App/CRA) that dynamically serves assets. It resolves paths relative to your project structure and often tolerant the pending minor inconsistencies (e.g., case sensitivity).
  • AWS Amplify Production: Amplify deploys a static build of your app (via npm run build). This build is served from an S3 bucket fronted by CloudFront (a CDN). Assets are minified, bundled, and paths are fixed at build time—no dynamic resolution. Inconsistencies in paths, file handling, or build configs that were ignored locally will break here.

2. Common Causes of Missing Images on Amplify#

Let’s dive into the most likely reasons your images fail on Amplify.

2.1 Incorrect Image Paths#

Local servers often resolve relative paths leniently, but production builds require precise pathing. For example:

  • Problem: Using relative paths like ../assets/image.jpg in your JSX. Locally, the dev server resolves this based on the current file’s location, but in the minified production build, folder structures may change (e.g., all JS is bundled into a single main.[hash].js file), breaking the path.
  • Example: If your component is in src/components/Header.js and the image is in src/assets/logo.png, ../assets/logo.png works locally. But in the build, Header.js is bundled into build/static/js/main.[hash].js, so ../assets no longer points to the correct folder.

2.2 Misconfigured Static Asset Handling#

React (especially with CRA) has strict rules for handling static assets like images. Two common mistakes here:

  • Assets in src/ but Not Imported: If you place images in src/ but reference them directly via a string path (e.g., <img src="./assets/image.jpg" />), the CRA build tooling won’t copy them to the build/ folder. Only imported assets or files in the public/ folder are included.
  • Incorrect Use of the public/ Folder: The public/ folder is for assets served as-is (e.g., favicon.ico, robots.txt). If you place images here but reference them with a path relative to src/ (e.g., public/assets/image.jpg referenced as ./public/assets/image.jpg), the build will fail.

2.3 AWS Amplify Build Configuration Errors#

Amplify requires explicit build settings to locate your app’s output. If these are misconfigured, the build may omit images entirely:

  • Incorrect Build Command: If your Amplify build settings use npm run dev instead of npm run build, it will deploy the development server (which won’t work in production).
  • Wrong Output Directory: CRA outputs builds to build/, but if Amplify is configured to look for dist/ or another folder, it will fail to find assets.
  • Missing Dependencies: If your package.json lacks dependencies required to process images (e.g., file-loader for webpack), the build may silently fail to bundle images.

2.4 CORS or Permissions Issues#

If your images are hosted externally (e.g., an S3 bucket or third-party CDN), CORS (Cross-Origin Resource Sharing) restrictions may block them on Amplify:

  • S3 Bucket CORS Misconfiguration: If the S3 bucket hosting your images doesn’t allow requests from your Amplify app’s domain, browsers will block the image with a CORS error (check the browser’s DevTools > Network tab for Access-Control-Allow-Origin issues).
  • Amplify App Permissions: Rarely, Amplify’s IAM roles or access settings may restrict access to assets, though this is more common for dynamic content than static images.

2.5 Caching and CDN Behavior#

AWS Amplify uses CloudFront as a CDN to cache assets for faster delivery. This can cause:

  • Stale Cached Assets: If you updated an image but didn’t change its filename, CloudFront may serve the old (deleted) version from cache, leading to a 404.
  • Incorrect Cache Headers: If your build outputs images with overly aggressive cache headers (e.g., Cache-Control: max-age=31536000), browsers may not fetch the latest version after deployment.

2.6 Case Sensitivity in File Names#

Local filesystems (macOS/Windows) are case-insensitive (e.g., Logo.png and logo.png are treated as the same). However:

  • AWS Amplify Runs on Linux: Linux filesystems are case-sensitive. If your code references logo.png but the actual file is Logo.png, Amplify will throw a 404, even if it worked locally.

3. Step-by-Step Troubleshooting Guide#

Follow these steps to diagnose and fix missing images on Amplify:

Step 1: Inspect the Broken Image in DevTools#

Right-click the broken image and select "Inspect" to check its src attribute. Look for:

  • 404 Errors: The path is invalid (see Section 2.1/2.2).
  • CORS Errors: Check the Network tab for (blocked: CORS) or No 'Access-Control-Allow-Origin' header (see Section 2.4).
  • Incorrect Domain: The src points to localhost instead of your Amplify domain (indicates hardcoded local paths).

Step 2: Verify Image Paths#

Fix 1: Import Images as Modules (Best for src/ Assets)#

If your images are in src/ (e.g., src/assets/), import them as modules so webpack handles path resolution:

// src/components/Header.js
import logo from '../assets/logo.png'; // Import the image
 
function Header() {
  return <img src={logo} alt="Logo" />; // Use the imported variable
}

Webpack will copy the image to build/static/media/ and generate a hashed filename (e.g., logo.[hash].png), ensuring the path works in production.

Fix 2: Use the public/ Folder for Static Assets#

If images are in public/ (e.g., public/images/background.jpg), reference them using absolute paths relative to public/:

// Correct: Path is relative to public/
<img src="/images/background.jpg" alt="Background" />
 
// Better: Use process.env.PUBLIC_URL for subpath deployments
<img src={`${process.env.PUBLIC_URL}/images/background.jpg`} alt="Background" />

process.env.PUBLIC_URL ensures the path works even if your app is deployed to a subpath (e.g., https://yourapp.amplifyapp.com/blog).

Step 3: Validate the Build Output#

Run npm run build locally to generate the build/ folder. Check if your images exist:

  • Imported Images: Look in build/static/media/ (hashed filenames).
  • public/ Images: Look directly in build/ (e.g., build/images/background.jpg).

If images are missing here, the issue is in your local build config—not Amplify. Fix this first!

Step 4: Check Amplify Build Settings#

  1. Go to the AWS Amplify Console.
  2. Select your app > Build settings.
  3. Ensure the amplify.yml config matches this (for CRA apps):
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - npm ci # Install dependencies
    build:
      commands:
        - npm run build # Build the app
  artifacts:
    baseDirectory: build # Output folder (CRA uses "build")
    files:
      - '**/*' # Include all files in the build folder
  cache:
    paths:
      - node_modules/**/* # Cache dependencies

If using a custom React setup (e.g., Vite, Next.js), update baseDirectory (e.g., dist for Vite) and build commands accordingly.

Step 5: Fix Case Sensitivity#

Rename image files and their references to use consistent case (e.g., logo.png everywhere, not Logo.png or LOGO.png).

Step 6: Resolve CORS Issues (External Images)#

If images are hosted on S3:

  1. Go to the S3 Console.
  2. Select your bucket > Permissions > CORS configuration.
  3. Add a rule allowing your Amplify domain:
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET"],
    "AllowedOrigins": ["https://your-amplify-app-id.amplifyapp.com"],
    "MaxAge": 3000
  }
]

Step 7: Bypass CloudFront Caching#

If old images are cached:

  1. In the Amplify Console, go to App settings > Domain management.
  2. Under CloudFront distribution, click the distribution ID.
  3. In CloudFront, go to Invalidations > Create Invalidation > Enter /* to invalidate all cached assets.

4. Prevention Tips for Future Deployments#

  • Always Test Builds Locally: Run npm run build and serve the build/ folder with npx serve before deploying to catch path issues early.
  • Use Import Statements for src/ Assets: This ensures webpack handles pathing and avoids manual errors.
  • Stick to public/ for Static, Unchanging Assets: Use it for logos, favicons, or assets referenced in index.html.
  • Normalize File Names: Use lowercase with hyphens (e.g., header-bg.jpg) to avoid case-sensitivity issues.
  • Check the Network Tab: After deploying, use DevTools to verify images load with 200 status codes.

5. Conclusion#

Missing images on AWS Amplify are almost always due to pathing, build config, or asset-handling issues—not Amplify itself. By following this guide, you can systematically diagnose whether the problem lies in how you reference images, configure your build, or handle caching. Remember: what works locally may not work in production, so validate builds and use React’s asset conventions.

6. References#