DEV Community

Jaimin Patel
Jaimin Patel

Posted on • Originally published at npmjs.com

React Smart Image: The Free React Image Optimization Library Every Developer Should Try

If you've built more than one React app, you've probably written the same image-loading boilerplate more than once.

A skeleton here. A lazy-loading IntersectionObserver there. A fallback image when the CDN hiccups. A blur placeholder to avoid layout shift. Retry logic for flaky hosts.

Every new React project ends up with the same image-loading code—copied from the last project, tweaked a little, and rarely perfect.

Sound familiar?

@concatstring/react-smart-image brings all of those features together in a single component. Simply swap <img> with <SmartImage> and opt into features like lazy loading, WebP detection, blur placeholders, skeleton loaders, retries, responsive images, and performance metrics—all without changing your existing code.

Getting started only takes a minute:

npm install @concatstring/react-smart-image
Enter fullscreen mode Exit fullscreen mode

Key Features

  • ✅ Drop-in replacement for <img>
  • ✅ Zero runtime dependencies
  • ✅ Fully typed with TypeScript
  • ✅ Responsive image support
  • ✅ Lazy loading
  • ✅ Skeleton loaders
  • ✅ Blur placeholders
  • ✅ Retry with exponential backoff
  • ✅ Error fallbacks
  • ✅ Automatic WebP detection
  • ✅ Image performance metrics
  • ✅ Works with React 18+

🔗 Live Demo

Why We Built React Smart Image

Across multiple React projects, we found ourselves implementing the same image-loading logic repeatedly. Some projects needed lazy loading, while others required blur placeholders, retry logic, responsive images, or skeleton loaders. Maintaining separate implementations for each project quickly became repetitive and difficult to scale.

We wanted a single component that behaves like a normal <img> while providing modern image optimization features only when needed.

That's why we built React Smart Image.

Instead of maintaining multiple utilities across projects, I now use one component everywhere and simply enable the features I need with a few props. The result is cleaner code, a more consistent developer experience, and better image performance out of the box.

Drop-in Replacement

Replacing your existing images is as simple as swapping the component:

import { SmartImage } from '@concatstring/react-smart-image';

<SmartImage src="/photo.jpg" alt="A photo" width={800} height={600} />
Enter fullscreen mode Exit fullscreen mode

className, style, onClick, loading, ref — all forwarded to the real <img>. Nothing new to learn. Enable additional features only when you need them—one prop at a time.

1. Lazy loading that actually waits for the viewport

<SmartImage src="/hero.jpg" alt="Hero" width={1200} height={600} lazy />
Enter fullscreen mode Exit fullscreen mode

Images load 100px before they scroll into view — powered by IntersectionObserver, no scroll listeners, no jank.

2. Skeleton loaders (with dark-mode support)

<SmartImage
  src="/avatar.jpg"
  alt="User avatar"
  width={80}
  height={80}
  skeleton
  skeletonColor="#1f2937"
  skeletonHighlightColor="rgba(255,255,255,0.12)"
/>
Enter fullscreen mode Exit fullscreen mode

An animated shimmer while the image loads — themeable to your brand or dark UI.

3. Blur placeholders — manual or automatic

Pass a tiny base64 LQIP the Next.js way:

<SmartImage src="/landscape.jpg" alt="Landscape" width={1200} height={800}
  placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQ..." />
Enter fullscreen mode Exit fullscreen mode

Or skip the base64 entirely. If your CDN resizes by URL (Cloudinary, imgix, CloudFront…), autoBlur derives the preview for you:

<SmartImage src="/landscape.jpg" alt="Landscape" width={1200} height={800}
  placeholder="blur" autoBlur blurWidth={24} />
Enter fullscreen mode Exit fullscreen mode

4. Retry with exponential backoff + fallback

CDNs fail. Handle it declaratively instead of with a tangle of onError handlers:

<SmartImage
  src="/flaky-cdn.jpg"
  alt="Product"
  width={400}
  height={300}
  retry={3}
  retryDelay={500}          // 500ms → 1s → 2s
  fallback="/placeholder.png"
/>
Enter fullscreen mode Exit fullscreen mode

5. WebP auto-detection

Try the modern format first, fall back automatically if it 404s:

<SmartImage src="/photo.jpg" alt="Product" width={600} height={400} webp />
Enter fullscreen mode Exit fullscreen mode

6. Responsive images — two strategies

The default srcset strategy lets the browser pick (DPR-aware). The viewport strategy always serves the smallest bytes per screen, ignoring device pixel ratio:

<SmartImage
  src="/banner.jpg"
  alt="Banner"
  responsive
  strategy="viewport"
  sizes={{ mobile: 480, tablet: 768, desktop: 1200 }}
/>
Enter fullscreen mode Exit fullscreen mode
Screen srcset (default) viewport
Mobile @1× 480w 480w
Mobile @2× 1200w (DPR-aware) 480w
Desktop 1200w 1200w

Host encodes size in the path instead of ?w=? Point it wherever you want with srcSetBuilder:

srcSetBuilder={(src, width) => src.replace(/(\.\w+)$/, `-${width}$1`)}
// /photo.jpg → /photo-480.jpg
Enter fullscreen mode Exit fullscreen mode

Compose everything

The features are orthogonal — stack them:

<SmartImage
  src="/product.jpg"
  alt="Product photo"
  width={600}
  height={600}
  lazy
  skeleton
  webp
  fallback="/placeholder.png"
  retry={2}
  retryDelay={1000}
  onLoadInfo={({ loadTime, fromCache }) =>
    console.log(`Loaded in ${loadTime}ms (cache: ${fromCache})`)
  }
/>
Enter fullscreen mode Exit fullscreen mode

That onLoadInfo callback hands you loadTime, naturalWidth/Height, and a fromCache flag — real performance telemetry per image, for free.

Why not just use next/image?

next/image is great — if you're on Next.js. react-smart-image works in any React app: Vite, CRA, Remix, Gatsby, a plain SPA. No image server required, no framework lock-in, no runtime dependencies. Think of it as a modern, framework-agnostic upgrade to the standard <img> element.

Try it

npm install @concatstring/react-smart-image
Enter fullscreen mode Exit fullscreen mode

If React Smart Image saves you from writing the same image-loading code again, I'd really appreciate a ⭐ on GitHub.

Feedback, feature requests, and bug reports are always welcome. If there's a feature you'd like to see, let me know in the comments or open an issue.

Built by the team at Concatstring.

Top comments (1)

Collapse
 
topstar_ai profile image
Luis

This is a solid direction for solving a real pain point in React apps—image performance is often underestimated until it breaks UX. What I like about this approach is the focus on abstraction: wrapping optimization, lazy loading, and responsive behavior into a reusable component instead of pushing that burden onto every developer. That said, image optimization is a deep space—CDN-level resizing, format negotiation (WebP/AVIF), and proper caching strategies still matter a lot beyond the React layer. Curious how this compares with Next.js Image or modern CDN-based solutions. Still, great idea for teams not using a full framework stack.