How to Import Images in React JS
Learn the correct way to import images in React with Vite, public assets, dynamic URLs, SVGs, troubleshooting, and production guidance.
In a Vite React app, import a local image in the JavaScript module and pass the resulting URL to the src prop:
import photoUrl from './photo.png';
export default function Profile() {
return <img src={photoUrl} alt="A short description of the image" />;
}
The import is handled by your bundler, not by React itself. Vite adds the referenced file to its asset graph, emits a production URL, and may include a content hash in the filename. The development URL and production URL can therefore differ. See Vite’s Static Asset Handling guide and React’s common component reference.
1. Import an image from the same component folder
Place the file beside the component or in a nearby assets directory, then use a relative import:
src/
components/
Profile.jsx
avatar.png
import avatarUrl from './avatar.png';
export default function Profile() {
return (
<figure>
<img src={avatarUrl} alt="Portrait of Alex Morgan" width={160} height={160} />
<figcaption>Alex Morgan</figcaption>
</figure>
);
}
Use a relative path from the JavaScript file. ./avatar.png means “in this module’s directory”; ../assets/avatar.png moves up one directory first.
Common image formats
Vite can process common static assets such as PNG, JPEG, GIF, SVG and WebP. The imported value is a URL string:
import logoUrl from './logo.svg';
console.log(typeof logoUrl); // "string"
export default function Header() {
return <img src={logoUrl} alt="Company logo" />;
}
2. Put the file in public when it needs a fixed URL
Vite’s public directory is copied to the build output without changing filenames. A file at public/photo.png is referenced from the site root:
public/
photo.png
export default function Profile() {
return <img src="/photo.png" alt="A short description of the image" />;
}
Use public when an exact, stable filename is required, when another system expects a root URL, or when the file is not naturally owned by a component. Vite recommends ordinary imports unless you specifically need the guarantees of the public directory. Public files are copied as-is, so they do not receive the same import-driven asset analysis or generated content hash.
| Situation | Recommended pattern | Result |
|---|---|---|
| Image belongs to a component and is known at build time | import imageUrl from './image.png' |
Tracked in the asset graph and emitted with a production URL |
| Stable filename or root URL is required | public/image.png plus src="/image.png" |
Copied without renaming |
| Runtime-selected image names | Use an explicit import map or Vite’s supported glob mechanism | Only analyzable files are included |
3. Handle several known images with an import map
Do not build an arbitrary import path from user input and expect Vite to discover every file. Static imports are easiest to analyze. For a small, fixed set, create an import map:
import lightUrl from './light.png';
import darkUrl from './dark.png';
const themeImages = {
light: lightUrl,
dark: darkUrl,
};
export default function ThemeImage({ theme = 'light' }) {
const imageUrl = themeImages[theme] ?? themeImages.light;
return <img src={imageUrl} alt="Theme illustration" />;
}
This keeps every file visible to the bundler and gives you a safe fallback for an unknown key.
4. Use new URL() for a static module-relative URL
Vite supports this browser pattern when the path is statically analyzable:
const photoUrl = new URL('./photo.png', import.meta.url).href;
export default function Profile() {
return <img src={photoUrl} alt="A short description of the image" />;
}
The filename must be a literal or otherwise analyzable reference. A value such as new URL(imagePath, import.meta.url) where imagePath is an arbitrary runtime string may not be transformed into a shipped asset. Vite also documents that this browser pattern does not work for Vite SSR because import.meta.url has different browser and Node.js semantics. In an SSR application, follow the framework’s server/client asset guidance.
5. Import images in TypeScript
Vite projects normally include declarations for common asset imports. If your TypeScript setup reports that a PNG or SVG module cannot be found, add an ambient declaration:
// src/vite-env.d.ts
/// <reference types="vite/client" />
Then use the imported URL normally:
import heroUrl from './hero.webp';
export function Hero() {
return <img src={heroUrl} alt="Product dashboard preview" />;
}
6. SVG files: URL versus React component
An SVG imported by Vite’s standard asset handling is a URL:
import iconUrl from './icon.svg';
export default function Icon() {
return <img src={iconUrl} alt="Settings" />;
}
Some toolchains add a special SVG-to-component transform. Create React App documents a ReactComponent named import, but that convention is webpack-specific and Create React App is deprecated. Do not copy that syntax into every React project. Check the documentation for the bundler or SVG plugin used by your application.
7. Avoid the /src/... URL mistake
This often works only by accident during development:
<img src="/src/assets/photo.png" alt="Example" />
Source files are meant to enter the build through imports. In production, Vite emits them under the configured assets directory and can rename them. Use an import:
import photoUrl from './assets/photo.png';
export default function Card() {
return <img src={photoUrl} alt="Example" />;
}
8. Complete Vite example
Create a Vite React project, add src/assets/hero.png, and render it from a component:
npm create vite@latest react-images -- --template react
cd react-images
npm install
npm run dev
// src/App.jsx
import heroUrl from './assets/hero.png';
import './App.css';
export default function App() {
return (
<main>
<h1>Image import example</h1>
<img
src={heroUrl}
alt="A landscape used in the image import example"
width={960}
height={540}
/>
</main>
);
}
npm run build
npm run preview
The production build resolves the imported file to the URL generated by Vite. Asset inlining and the inline-size threshold are configurable; do not assume one universal byte limit across projects.
9. Accessibility and rendering details
Give meaningful images concise alternative text. For purely decorative images, use an empty alt="" so assistive technology can skip them. Set intrinsic width and height when possible to reserve layout space, and use CSS for responsive sizing:
.heroImage {
display: block;
width: 100%;
height: auto;
}
10. Troubleshooting
“Failed to resolve import”
Cause: The relative path, filename case, or extension is wrong.
Fix: Check the path from the importing file, verify capitalization on case-sensitive systems, and confirm the file exists under the project directory.
The browser requests /src/... and receives 404
Cause: A source path was hard-coded into src.
Fix: Import the file from the module, or move it to public and reference it from the root URL.
The image works in development but breaks after deployment
Cause: The deployment uses a non-root base path, or the application bypassed bundler processing with a hard-coded URL.
Fix: Prefer an import for source assets and configure Vite’s base option for the hosting path. For public files, ensure the root-relative URL matches the deployed base path.
A dynamic filename is not included in the build
Cause: Vite cannot statically analyze an arbitrary runtime string.
Fix: Use an explicit import map, a statically analyzable new URL() reference, or the asset-glob feature documented by your Vite version.
new URL(..., import.meta.url) fails in SSR
Cause: The browser-oriented URL semantics differ from Node.js during server rendering.
Fix: Follow your SSR framework’s asset handling guidance and avoid assuming this browser pattern works on the server.
SVG component syntax does not compile
Cause: ReactComponent is a Create React App convention, not universal React syntax.
Fix: Import the SVG as a URL, or install and configure the SVG plugin supported by your current bundler.
The image is visually stretched
Cause: CSS dimensions do not preserve the source aspect ratio.
Fix: Set one dimension to auto, use object-fit when cropping is intended, and include intrinsic dimensions where practical.
11. Performance, reliability and deployment notes
- Static imports let the bundler fingerprint referenced files, so browser caches can safely retain a file until its content changes.
- Keep large images in appropriate formats and dimensions; importing a file does not automatically make its pixels smaller.
- Use
publicfor files whose exact URL is part of an external contract, such as a well-known asset path. - Test both
npm run devand a production build withnpm run buildandnpm run preview; development serving can hide path and base-URL mistakes. - For SSR, verify the framework’s server and client asset behavior instead of relying on browser-only
import.meta.urlassumptions. - Use stable keys and explicit fallbacks when choosing images from data so a missing record does not produce a broken image URL.
12. Or skip the browser setup
If your goal is to obtain a screenshot of a React page rather than bundle an image into the application, ScreenshotNeo returns a PNG, JPEG, WebP or PDF from one GET request. The API also accepts the parameter names used by other screenshot APIs, which can simplify migration.
See the ScreenshotNeo API documentation for all options. A minimal request looks like this:
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await Bun.write('shot.webp', data);
ScreenshotNeo removes cookie and consent banners, newsletter popups and chat widgets before capture. Bot checks, blank pages and failed loads are not billed, and response headers report the page verdict and whether the request was billed. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
13. FAQ
Is importing an image a React feature?
No. React renders the URL; the bundler decides how a local file becomes that URL.
Should every image go in src?
No. Use a source import for component-owned assets and public when a stable, unchanged filename is required.
Can I write src="./photo.png" directly in JSX?
Only when the resulting URL is valid for the rendered document. For a local source asset in Vite, importing the file is the dependable pattern.
Why does the production filename change?
Vite can emit imported assets with content hashes so browsers can cache them safely and fetch a new URL when the file changes.
Can I use the same import syntax in every React toolchain?
No. Asset resolution is tool-dependent. Check the current bundler or framework documentation, especially for SVG transforms and SSR.


