DEV Community

Cover image for Advanced Image Optimization in React/Next.js: Device-Based Responsive Images for Peak Performance (Part 1)
sizan mahmud0
sizan mahmud0

Posted on

Advanced Image Optimization in React/Next.js: Device-Based Responsive Images for Peak Performance (Part 1)

Images account for 50-70% of a typical web page's total size. Serving optimized images based on device capabilities and screen density is crucial for performance, user experience, and SEO. In this comprehensive guide, we'll explore device pixel ratio (DPR) optimization and responsive image techniques using React and Next.js.

Understanding Device Pixel Ratio (DPR)

Device Pixel Ratio represents the relationship between physical pixels and CSS pixels. Modern devices have varying DPRs:

  • Standard displays: DPR 1 (1920x1080 monitors)
  • Retina/HD displays: DPR 2 (MacBook Pro, iPhone)
  • Ultra HD displays: DPR 3+ (iPhone 15 Pro, Samsung Galaxy)

Serving a 1x image to a 3x device results in blurry visuals, while serving a 3x image to a 1x device wastes bandwidth.

Detecting Device Pixel Ratio in JavaScript

// Get current device pixel ratio
const dpr = window.devicePixelRatio || 1;

// Listen for DPR changes (when moving between monitors)
const dprQuery = window.matchMedia(`(resolution: ${dpr}dppx)`);
dprQuery.addEventListener('change', (e) => {
  if (!e.matches) {
    console.log('DPR changed, reload optimized images');
  }
});
Enter fullscreen mode Exit fullscreen mode

React Hook for Device Detection

Create a custom hook to detect device capabilities:

// hooks/useDeviceInfo.js
import { useState, useEffect } from 'react';

export const useDeviceInfo = () => {
  const [deviceInfo, setDeviceInfo] = useState({
    dpr: 1,
    width: 0,
    isMobile: false,
    isTablet: false,
    isDesktop: false,
    connection: 'unknown'
  });

  useEffect(() => {
    const updateDeviceInfo = () => {
      const width = window.innerWidth;
      const dpr = window.devicePixelRatio || 1;

      // Get connection speed (if supported)
      const connection = navigator.connection?.effectiveType || 'unknown';

      setDeviceInfo({
        dpr: Math.min(dpr, 3), // Cap at 3x for practicality
        width,
        isMobile: width < 768,
        isTablet: width >= 768 && width < 1024,
        isDesktop: width >= 1024,
        connection
      });
    };

    updateDeviceInfo();
    window.addEventListener('resize', updateDeviceInfo);

    return () => window.removeEventListener('resize', updateDeviceInfo);
  }, []);

  return deviceInfo;
};
Enter fullscreen mode Exit fullscreen mode

Building a Responsive Image Component (React)

// components/OptimizedImage.jsx
import React, { useState, useEffect } from 'react';
import { useDeviceInfo } from '../hooks/useDeviceInfo';

const OptimizedImage = ({ 
  src, 
  alt, 
  sizes = '100vw',
  quality = 75,
  className = '' 
}) => {
  const { dpr, width, connection } = useDeviceInfo();
  const [imageSrc, setImageSrc] = useState('');
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // Reduce quality on slow connections
    const adjustedQuality = connection === '2g' ? 50 : quality;

    // Calculate optimal image width
    const imageWidth = calculateImageWidth(width, dpr);

    // Build optimized URL (example with Cloudinary)
    const optimizedSrc = buildOptimizedUrl(src, {
      width: imageWidth,
      quality: adjustedQuality,
      dpr: dpr,
      format: 'auto' // WebP when supported
    });

    setImageSrc(optimizedSrc);
  }, [src, dpr, width, connection, quality]);

  const calculateImageWidth = (viewportWidth, deviceDpr) => {
    // Round to nearest standard size for better caching
    const targetWidth = viewportWidth * deviceDpr;
    const standardSizes = [320, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];

    return standardSizes.find(size => size >= targetWidth) || 3840;
  };

  const buildOptimizedUrl = (originalSrc, options) => {
    // Example for Cloudinary
    // Adapt this for your CDN (Imgix, Cloudflare Images, etc.)
    const { width, quality, dpr, format } = options;
    return `https://res.cloudinary.com/your-cloud/image/upload/w_${width},q_${quality},dpr_${dpr},f_${format}/${originalSrc}`;
  };

  return (
    <div className={`image-container ${className}`}>
      {isLoading && (
        <div className="skeleton-loader" aria-label="Loading image" />
      )}
      <img
        src={imageSrc}
        alt={alt}
        loading="lazy"
        onLoad={() => setIsLoading(false)}
        style={{ display: isLoading ? 'none' : 'block' }}
      />
    </div>
  );
};

export default OptimizedImage;
Enter fullscreen mode Exit fullscreen mode

Using Picture Element for Art Direction

Different devices might need different image crops or compositions:

const ResponsiveArtDirectedImage = ({ images, alt }) => {
  const { dpr } = useDeviceInfo();

  return (
    <picture>
      {/* Mobile - portrait crop */}
      <source
        media="(max-width: 767px)"
        srcSet={`
          ${images.mobile}?w=400&dpr=${dpr} 1x,
          ${images.mobile}?w=800&dpr=${dpr} 2x
        `}
      />

      {/* Tablet - landscape crop */}
      <source
        media="(min-width: 768px) and (max-width: 1023px)"
        srcSet={`
          ${images.tablet}?w=768&dpr=${dpr} 1x,
          ${images.tablet}?w=1536&dpr=${dpr} 2x
        `}
      />

      {/* Desktop - full width */}
      <source
        media="(min-width: 1024px)"
        srcSet={`
          ${images.desktop}?w=1200&dpr=${dpr} 1x,
          ${images.desktop}?w=2400&dpr=${dpr} 2x
        `}
      />

      <img src={images.desktop} alt={alt} loading="lazy" />
    </picture>
  );
};
Enter fullscreen mode Exit fullscreen mode

Continue to Part 2 for Next.js Image optimization, lazy loading strategies, and performance monitoring techniques.


Keywords: React image optimization, device pixel ratio, responsive images, Next.js performance, DPR optimization, lazy loading, web vitals

Top comments (0)