Imageslowoptimization
Handle image loading errors gracefully
rule · error-images
Broken images should fail gracefully with fallback content instead of showing broken icons.
Code Example
HTML
<!-- ❌ Bad: No fallback for broken image -->
<img src="product.jpg" alt="Product">
<!-- ✅ Good: Fallback image on error -->
<img
src="product.jpg"
alt="Product"
onerror="this.src='/images/placeholder.png'; this.onerror=null;"
>Why It Matters
Broken image icons look unprofessional and confuse users—graceful fallbacks maintain visual consistency and user trust when images fail to load.
React Error Handling Component
TSX
interface ImageWithFallbackProps {
src: string
fallback: string
alt: string
className?: string
}
function ImageWithFallback({
src,
fallback,
alt,
className
}: ImageWithFallbackProps) {
const [imgSrc, setImgSrc] = useState(src)
const [hasError, setHasError] = useState(false)
const handleError = () => {
if (!hasError) {
setImgSrc(fallback)
setHasError(true)
}
}
return (
<img
src={imgSrc}
alt={alt}
onError={handleError}
className={className}
/>
)
}
// Usage
<ImageWithFallback
src={product.image}
fallback="/images/product-placeholder.png"
alt={product.name}
/>Advanced Image Component with States
TSX
type ImageState = 'loading' | 'loaded' | 'error'
interface SmartImageProps {
src: string
alt: string
fallback?: string
placeholder?: React.ReactNode
className?: string
}
function SmartImage({
src,
alt,
fallback = '/images/placeholder.png',
placeholder,
className
}: SmartImageProps) {
const [state, setState] = useState<ImageState>('loading')
const [currentSrc, setCurrentSrc] = useState(src)
const handleLoad = () => setState('loaded')
const handleError = () => {
if (currentSrc !== fallback) {
setCurrentSrc(fallback)
} else {
setState('error')
}
}
if (state === 'error') {
return (
<div className={`image-error ${className}`} role="img" aria-label={alt}>
<span>Image unavailable</span>
</div>
)
}
return (
<div className={`image-wrapper ${className}`}>
{state === 'loading' && (placeholder || <div className="skeleton" />)}
<img
src={currentSrc}
alt={alt}
onLoad={handleLoad}
onError={handleError}
style={{ opacity: state === 'loaded' ? 1 : 0 }}
/>
</div>
)
}Next.js Image Error Handling
TSX
import Image from 'next/image'
function ProductImage({ src, alt }: { src: string; alt: string }) {
const [imgSrc, setImgSrc] = useState(src)
return (
<Image
src={imgSrc}
alt={alt}
width={400}
height={300}
onError={() => setImgSrc('/images/product-fallback.png')}
/>
)
}CSS Fallback for Background Images
CSS
/* Fallback background if image fails */
.hero {
background-color: #f0f0f0; /* Fallback color */
background-image: url('hero.jpg');
background-size: cover;
}
/* Styled broken image fallback */
img {
min-height: 100px;
background-color: #f5f5f5;
}
img::before {
content: '';
display: block;
}
img::after {
content: attr(alt);
display: block;
font-size: 14px;
color: #666;
text-align: center;
padding: 20px;
}Avatar with Initials Fallback
TSX
interface AvatarProps {
src?: string
name: string
size?: number
}
function Avatar({ src, name, size = 48 }: AvatarProps) {
const [showFallback, setShowFallback] = useState(!src)
const initials = name
.split(' ')
.map(part => part[0])
.join('')
.toUpperCase()
.slice(0, 2)
if (showFallback) {
return (
<div
className="avatar-fallback"
style={{ width: size, height: size }}
aria-label={name}
>
{initials}
</div>
)
}
return (
<img
src={src}
alt={name}
width={size}
height={size}
className="avatar"
onError={() => setShowFallback(true)}
/>
)
}Error Logging
TypeScript
function logImageError(src: string, error: Event) {
// Log to monitoring service
console.error('Image failed to load:', src)
// Send to analytics
if (window.analytics) {
window.analytics.track('Image Load Error', {
src,
page: window.location.pathname
})
}
}
// Usage in component
<img
src={src}
alt={alt}
onError={(e) => {
logImageError(src, e)
handleFallback()
}}
/>Verification
- Block images in DevTools (Network tab → Block request URL)
- Test with invalid image URLs
- Verify fallback appears correctly
- Check that alt text is accessible when fallback shows
- Test slow network—loading states should appear