Detect and Adapt to Poor Network Conditions | OpsBlu Docs

Detect and Adapt to Poor Network Conditions

How to detect and adapt to poor network conditions. Covers the Network Information API, adaptive image loading, offline-first strategies, and...

What This Means

Connection quality detection allows your site to adapt to users' network conditions. Without it:

  • Mobile users download unnecessary large assets
  • Slow connections timeout on heavy resources
  • Users on data-saver mode waste bandwidth
  • Poor experience on unreliable networks

How to Diagnose

Network Information API

// Check for Network Information API support
const connection = navigator.connection ||
                   navigator.mozConnection ||
                   navigator.webkitConnection;

if (connection) {
  console.log({
    effectiveType: connection.effectiveType,  // '4g', '3g', '2g', 'slow-2g'
    downlink: connection.downlink,            // Mbps
    rtt: connection.rtt,                      // Round trip time in ms
    saveData: connection.saveData,            // Data saver enabled
    type: connection.type                     // 'wifi', 'cellular', etc.
  });
}

Connection Types

effectiveType Typical Use Case
4g Full experience, high-res images
3g Standard experience, optimized images
2g Minimal experience, critical content only
slow-2g Text-only or offline mode

General Fixes

Adaptive Image Loading

// Load different image sizes based on connection
function getOptimizedImageUrl(baseUrl) {
  const connection = navigator.connection;

  if (!connection) {
    return `${baseUrl}?quality=80`;
  }

  if (connection.saveData) {
    return `${baseUrl}?quality=30&width=400`;
  }

  switch (connection.effectiveType) {
    case 'slow-2g':
    case '2g':
      return `${baseUrl}?quality=30&width=400`;
    case '3g':
      return `${baseUrl}?quality=60&width=800`;
    default:
      return `${baseUrl}?quality=80&width=1200`;
  }
}

// Usage
const imgUrl = getOptimizedImageUrl('/api/images/hero.jpg');

Conditional Feature Loading

// Load heavy features only on good connections
async function loadEnhancedFeatures() {
  const connection = navigator.connection;

  // Skip on slow connections or data saver
  if (connection?.saveData ||
      ['slow-2g', '2g'].includes(connection?.effectiveType)) {
    console.log('Skipping enhanced features due to slow connection');
    return;
  }

  // Load video backgrounds, animations, etc.
  await import('./enhanced-features.js');
}

// React with connection hook
function useConnectionQuality() {
  const [quality, setQuality] = useState('unknown');

  useEffect(() => {
    const connection = navigator.connection;
    if (!connection) {
      setQuality('unknown');
      return;
    }

    const updateQuality = () => {
      setQuality(connection.effectiveType);
    };

    updateQuality();
    connection.addEventListener('change', updateQuality);

    return () => {
      connection.removeEventListener('change', updateQuality);
    };
  }, []);

  return quality;
}

Preload Strategy Based on Connection

// Aggressive preloading on fast connections only
function setupPreloading() {
  const connection = navigator.connection;

  if (!connection || connection.effectiveType !== '4g' || connection.saveData) {
    return; // Don't preload on slow or metered connections
  }

  // Preload likely next pages
  const linksToPreload = document.querySelectorAll('a[data-preload]');
  linksToPreload.forEach(link => {
    const prefetch = document.createElement('link');
    prefetch.rel = 'prefetch';
    prefetch.href = link.href;
    document.head.appendChild(prefetch);
  });
}

Connection Change Handler

// React to connection changes
function setupConnectionMonitoring() {
  const connection = navigator.connection;
  if (!connection) return;

  connection.addEventListener('change', () => {
    console.log('Connection changed:', {
      type: connection.effectiveType,
      downlink: connection.downlink,
      rtt: connection.rtt
    });

    // Adjust behavior
    if (connection.effectiveType === 'slow-2g') {
      pauseVideoPlayback();
      reduceImageQuality();
    }

    // Show indicator to user
    if (connection.effectiveType === '2g' || connection.effectiveType === 'slow-2g') {
      showSlowConnectionBanner();
    }
  });
}

CSS-Based Adaptation

/* Use low-quality images by default (save-data) */
.hero-image {
  background-image: url('/images/hero-low.jpg');
}

/* Upgrade on fast connections via JS */
.hero-image.high-quality {
  background-image: url('/images/hero-high.jpg');
}

/* Reduce animations on slow connections */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
// Apply high-quality class based on connection
if (navigator.connection?.effectiveType === '4g' && !navigator.connection?.saveData) {
  document.querySelectorAll('.hero-image').forEach(el => {
    el.classList.add('high-quality');
  });
}

Offline Detection

// Handle offline state
function setupOfflineDetection() {
  window.addEventListener('online', () => {
    console.log('Back online');
    retryFailedRequests();
    hideOfflineBanner();
  });

  window.addEventListener('offline', () => {
    console.log('Gone offline');
    showOfflineBanner();
    queueRequestsForLater();
  });

  // Initial check
  if (!navigator.onLine) {
    showOfflineBanner();
  }
}

Browser Support

Feature Chrome Firefox Safari Edge
Network Information API Yes Limited No Yes
navigator.onLine Yes Yes Yes Yes
connection.saveData Yes No No Yes

Fallback strategy:

function getConnectionQuality() {
  const connection = navigator.connection;

  // Use Network Information API if available
  if (connection?.effectiveType) {
    return connection.effectiveType;
  }

  // Fallback: measure actual download speed
  return measureConnectionSpeed();
}

async function measureConnectionSpeed() {
  const startTime = performance.now();
  const response = await fetch('/speed-test.bin'); // ~100KB file
  const blob = await response.blob();
  const duration = performance.now() - startTime;
  const speedMbps = (blob.size * 8) / (duration * 1000);

  if (speedMbps > 5) return '4g';
  if (speedMbps > 1) return '3g';
  if (speedMbps > 0.1) return '2g';
  return 'slow-2g';
}

Verification

  1. Test with DevTools Network throttling
  2. Verify different assets load per connection
  3. Check saveData flag is respected
  4. Monitor connection change events

Further Reading