Recommended Free Tools
Use React’s built-in <img> element: provide a src URL and meaningful alt text. For a file in your project, either import it from src or place it in Vite’s public directory and reference a root-absolute URL. For data supplied at runtime, put the JavaScript value in braces, such as src={user.imageUrl}. The sections below show each approach, when to choose it, and how to diagnose images that do not appear.
The basic React image
React renders the browser’s normal image element. A minimal component is:
export default function ParkPhoto() {
return (
<img
src="/images/park.jpg"
alt="A person walking through a park"
/>
);
}
src is the address the browser requests. alt describes an informative image to people using screen readers and appears as a text fallback when the image cannot load. If an image is purely decorative, use alt="" rather than inventing a description.
When the dimensions are known, include width and height. The browser can reserve the correct space before the file arrives, reducing layout movement. For a below-the-fold, noncritical image, loading="lazy" can defer loading; fetchPriority="low" can also lower its loading priority. Keep important hero imagery eager unless your rendering stack says otherwise.
#1 Best Overall
Choose where the file lives
| Approach | Use it when | URL behavior | Main trade-off |
|---|---|---|---|
Import from src |
The image belongs to the application and is known when you build it | The bundler returns the final public URL | Stable source paths, but the production filename can be fingerprinted |
Vite public directory |
The file should be served directly or keep a stable name | Reference it with a root-absolute path such as /images/logo.png |
No Vite processing; a wrong path becomes a runtime 404 |
| Prop or fetched URL | The image is discovered from user data or an API | Evaluate the value with braces, for example src={user.imageUrl} |
The browser must be able to reach the remote address |
Vite is one common build-tool route for new React projects. Create React App is deprecated, so a new application should use a recommended framework or another supported setup rather than starting a new CRA project.
Method 1: import an image from src
Put the file under the component’s source tree, then import it with a static path:
import profilePhoto from './assets/profile-photo.jpg';
export default function Profile() {
return (
<img
src={profilePhoto}
alt="Profile portrait"
width={160}
height={160}
/>
);
}
Vite resolves that import to a public URL. During development it may look different from the production URL, because a production build can emit a content-hashed filename. Create React App’s documented import pattern works the same way: the imported value is passed to src.
Keep the import discoverable
Use a literal, static import path so the bundler can find the file:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsimport hero from './assets/hero.jpg';
Do not try to construct an arbitrary import path from a variable and expect the bundler to discover every possible file. If the filename is only known after an API response, use a URL value from that response instead.
A complete Vite-style component
import productPhoto from './assets/product-photo.webp';
export default function ProductCard() {
return (
<article>
<img
src={productPhoto}
alt="Blue ceramic travel mug"
width={320}
height={240}
loading="lazy"
/>
<h2>Travel mug</h2>
</article>
);
}
Method 2: serve a file from Vite’s public directory
Create this project layout:
your-app/
├─ public/
│ └─ images/
│ └─ logo.png
└─ src/
└─ App.jsx
Reference the file from the site root. Do not include public in the URL:
export default function Logo() {
return (
<img
src="/images/logo.png"
alt="Company logo"
width={220}
height={48}
/>
);
}
Vite serves public files directly, without processing them. This is useful when a filename must remain stable or direct public serving is intentional. In the legacy Create React App workflow, public files are likewise not post-processed or content-hashed; a typo is therefore a runtime 404, which is why imports remain the normal recommendation for application assets.
Routes and leading slashes
A root-absolute path such as /images/logo.png starts at the site origin. A path such as images/logo.png is relative to the current document URL and can break when the component is rendered on a nested route. If you intentionally need a route-relative URL, verify the resulting address in the browser’s Network panel; otherwise prefer the leading slash for a Vite public asset.
Rank #3
Method 3: render a remote or data-backed image
When a URL comes from props or fetched data, put the JavaScript expression in braces. Quotes create a literal string; braces evaluate a value:
function Avatar({ user }) {
return (
<img
src={user.imageUrl}
alt={user.name}
width={96}
height={96}
/>
);
}
A parent can pass the data like this:
export default function Account() {
const user = {
name: 'Mina Patel',
imageUrl: 'https://images.example.com/mina.jpg'
};
return <Avatar user={user} />;
}
For a remote image, check that the address is reachable by the browser and that it is the image you intend to expose. Framework-specific image components may add optimization or preload behavior and can use different defaults; follow that framework’s documentation when you replace the native element.
Handle a missing URL explicitly
Do not send an undefined value to src. Render a fallback or omit the image until data exists:
function UserAvatar({ user }) {
if (!user?.imageUrl) {
return <span aria-label="No profile image">—</span>;
}
return (
<img
src={user.imageUrl}
alt={`${user.name} profile portrait`}
width={96}
height={96}
/>
);
}
Accessibility and loading decisions
- Describe purpose, not decoration: write concise alternative text that conveys what an informative image contributes. Use
alt=""for a purely decorative image. - Reserve space: provide numeric
widthandheightwhen known. - Lazy-load selectively: use
loading="lazy"for below-the-fold, noncritical images. Do not automatically lazy-load the primary hero image. - Lower noncritical priority when appropriate:
fetchPriority="low"tells the browser the request is less urgent. - Use the correct attribute name: it is
src, neverscr.
These choices affect both users who rely on alternative text and users on slower connections. They do not change where the image file is stored; choose storage and loading behavior independently.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #4
Why an image is not showing
The page shows alt text or a broken-image icon
- Open the browser’s Network panel and inspect the image request. A 404 usually means the URL does not match the file’s served path.
- For a Vite public file, remove
public/from the URL:/images/logo.png, not/public/images/logo.png. - For an imported file, verify the import is relative to the component file and that capitalization matches the filename.
- For a remote value, copy the resolved URL from the element and open it directly in a new tab. Confirm that the data is not empty or undefined.
The image works on the home page but not another route
A relative URL changes meaning when the document path changes. Use a root-absolute public URL or import the asset from src. The Network panel shows the exact address the browser requested, which makes this error straightforward to distinguish from a missing file.
The imported file works in development but the URL looks different in production
That is expected: the production build can emit a hashed filename. Use the imported variable rather than hard-coding the development URL, so the bundler can substitute the correct production address.
The dynamic image is blank
Log or inspect the value supplied to src. Make sure you use braces—src={user.imageUrl}—rather than the literal text src="user.imageUrl". Also verify that the browser can reach the returned URL and that your fallback handles a missing value.
The layout jumps while images load
Add the known intrinsic width and height. Without dimensions, the browser cannot reserve the image’s space before the response arrives.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Best Value
A new project starts with an outdated setup
Create React App is deprecated. Start with a recommended React framework or a supported tool such as Vite, then apply the import or public-directory patterns above.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance and reliability checklist
- Decide whether the asset is build-time application content, a stable public file, or runtime data.
- Use an import for bundled application assets; use
publiconly when direct serving or a stable name is intentional. - Set
widthandheightwhenever the dimensions are known. - Mark below-the-fold, noncritical images with
loading="lazy"; keep critical imagery eager. - Use a concise, useful
altvalue, or an empty value for decoration. - For data-backed images, validate the URL before rendering and provide a visible fallback.
- When something fails, inspect the final request URL and status in the browser’s Network panel before changing code.
Capture a rendered React page without setting up a browser
If your goal is to create a screenshot of a deployed React route for documentation, QA, or a report, ScreenshotNeo is a website screenshot API and MCP server for developers. It accepts a URL and returns a PNG, JPEG, WebP, or PDF. Its cleanup step accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.
Or skip the browser setup
Make one request (the complete option list and authentication details are in the ScreenshotNeo API documentation):
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
For a React page, replace the example URL with the route you want to capture. ScreenshotNeo supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus arbitrary viewports, retina scale, PDF paper size and page ranges, custom CSS and JavaScript, clicks before capture, selector waits, delays or network-idle waits, request and resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed public-image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work to ease migration. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
| Plan | Included shots | Price |
|---|---|---|
| Free | 1,000 per month | No card required |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Every feature is included on every plan, and yearly billing provides two months free. Start with 1,000 free screenshots a month with no card; paid plans start at $5 for 3,000.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




