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#
- Understanding the Local vs. Amplify Environment
- Common Causes of Missing Images on Amplify
- Step-by-Step Troubleshooting Guide
- Prevention Tips for Future Deployments
- Conclusion
- 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-serverfor 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.jpgin 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 singlemain.[hash].jsfile), breaking the path. - Example: If your component is in
src/components/Header.jsand the image is insrc/assets/logo.png,../assets/logo.pngworks locally. But in the build,Header.jsis bundled intobuild/static/js/main.[hash].js, so../assetsno 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 insrc/but reference them directly via a string path (e.g.,<img src="./assets/image.jpg" />), the CRA build tooling won’t copy them to thebuild/folder. Only imported assets or files in thepublic/folder are included. - Incorrect Use of the
public/Folder: Thepublic/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 tosrc/(e.g.,public/assets/image.jpgreferenced 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 devinstead ofnpm 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 fordist/or another folder, it will fail to find assets. - Missing Dependencies: If your
package.jsonlacks dependencies required to process images (e.g.,file-loaderfor 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-Originissues). - 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.pngbut the actual file isLogo.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)orNo 'Access-Control-Allow-Origin' header(see Section 2.4). - Incorrect Domain: The
srcpoints tolocalhostinstead 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#
- Go to the AWS Amplify Console.
- Select your app > Build settings.
- Ensure the
amplify.ymlconfig 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 dependenciesIf 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:
- Go to the S3 Console.
- Select your bucket > Permissions > CORS configuration.
- 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:
- In the Amplify Console, go to App settings > Domain management.
- Under CloudFront distribution, click the distribution ID.
- 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 buildand serve thebuild/folder withnpx servebefore 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 inindex.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.