JavaScript

Detecting Network Quality in JavaScript: The Network Information API and Adaptive Asset Loading

DD
Ankur Ishwar
7 min read Updated Sep 6, 2026
Detecting network quality and adaptive asset loading in JavaScript

The 5G Detection Myth in Browser JavaScript

A common misconception in web development is that browsers provide a direct property indicating whether a device is connected to a physical 5G cell tower. Online forums frequently post snippets assuming navigator.connection.effectiveType === '5g' is valid syntax. In reality, no browser specification has ever defined a 5G string return value.

Under the W3C Network Information API specification, the effectiveType attribute only yields four discrete tokens: slow-2g, 2g, 3g, and 4g. The specification authors intentionally decoupled this property from physical radio infrastructure (like LTE, 5G NR, or Wi-Fi 6). Instead, effectiveType categorizes network quality by measuring round-trip latency and transfer bandwidth. Even on a millimeter-wave 5G connection with gigabit speeds, Chromium browsers report effectiveType: '4g'.

Understanding this distinction is the key to building high-performance web applications that adapt intelligently to real client conditions.

The Architecture of the Network Information API

Modern Chromium engines (Google Chrome, Microsoft Edge, Opera, Brave) expose connection telemetry through the navigator.connection interface:

window.navigator.connection
   │
   ├── effectiveType : '4g' | '3g' | '2g' | 'slow-2g'
   ├── downlink      : Estimated bandwidth in megabits per second (e.g. 10.0)
   ├── rtt           : Estimated round-trip latency in milliseconds (e.g. 50)
   ├── saveData      : Boolean indicating user data-saver preferences
   └── onchange      : Event listener fired on network condition changes

Because Safari (WebKit) and Firefox (Gecko) currently decline to expose this API due to browser fingerprinting safeguards, you must wrap network access logic in strict TypeScript defensive checks.

Step 1: Implementing a Type-Safe Network Quality Service

Here is a complete, production-ready TypeScript monitor that wraps the browser API with graceful fallbacks and active latency testing:

// src/services/network-monitor.ts
export type EffectiveTier = '4g' | '3g' | '2g' | 'slow-2g' | 'offline' | 'unknown';

export interface NetworkSnapshot {
  online: boolean;
  effectiveType: EffectiveTier;
  downlinkMbps: number | null;
  rttMs: number | null;
  saveData: boolean;
}

// Augment Navigator interface for browsers supporting Network Information API
interface NetworkInformation extends EventTarget {
  readonly effectiveType?: EffectiveTier;
  readonly downlink?: number;
  readonly rtt?: number;
  readonly saveData?: boolean;
  onchange?: ((this: NetworkInformation, ev: Event) => any) | null;
}

interface NavigatorWithConnection extends Navigator {
  readonly connection?: NetworkInformation;
  readonly mozConnection?: NetworkInformation;
  readonly webkitConnection?: NetworkInformation;
}

export class NetworkMonitor {
  private connection: NetworkInformation | null = null;
  private listeners: Set<(snapshot: NetworkSnapshot) => void> = new Set();

  constructor() {
    const nav = typeof navigator !== 'undefined' ? (navigator as NavigatorWithConnection) : null;
    this.connection = nav?.connection || nav?.mozConnection || nav?.webkitConnection || null;
    this.initListeners();
  }

  public getSnapshot(): NetworkSnapshot {
    const isOnline = typeof navigator !== 'undefined' ? navigator.onLine : true;
    if (!isOnline) {
      return {
        online: false,
        effectiveType: 'offline',
        downlinkMbps: 0,
        rttMs: null,
        saveData: false,
      };
    }

    return {
      online: true,
      effectiveType: this.connection?.effectiveType || '4g',
      downlinkMbps: this.connection?.downlink ?? null,
      rttMs: this.connection?.rtt ?? null,
      saveData: this.connection?.saveData ?? false,
    };
  }

  public subscribe(callback: (snapshot: NetworkSnapshot) => void): () => void {
    this.listeners.add(callback);
    // Immediately notify with current state
    callback(this.getSnapshot());
    return () => this.listeners.delete(callback);
  }

  private notify() {
    const snapshot = this.getSnapshot();
    for (const listener of this.listeners) {
      listener(snapshot);
    }
  }

  private initListeners() {
    if (typeof window === 'undefined') return;
    window.addEventListener('online', () => this.notify());
    window.addEventListener('offline', () => this.notify());

    if (this.connection) {
      this.connection.addEventListener('change', () => this.notify());
    }
  }
}

Step 2: Fallback Latency Probes for Safari and Firefox

When running on browsers where navigator.connection is undefined, you can calculate real network latency using a lightweight HTTP HEAD request against a global edge CDN (such as Cloudflare or AWS CloudFront):

// src/services/ping-probe.ts
export async function measureActualLatency(probeUrl = 'https://cloudflare.com/cdn-cgi/trace'): Promise<number> {
  const cacheBustUrl = `${probeUrl}?_t=${Date.now()}`;
  const start = performance.now();

  try {
    await fetch(cacheBustUrl, {
      method: 'HEAD',
      mode: 'no-cors',
      cache: 'no-store',
    });
    const duration = Math.round(performance.now() - start);
    return duration;
  } catch (err) {
    throw new Error('Network probe failed: Host unreachable');
  }
}

Step 3: Adaptive Asset Loading in Practice

The true utility of network detection is performance optimization. Forcing a user on a congested 3G mobile connection to download uncompressed 4K hero banners or autoplaying background videos drains mobile battery and burns data allowances.

Here is an adaptive image loader component that swaps asset resolutions dynamically based on measured client capabilities:

// src/components/adaptive-image.ts
import { NetworkMonitor, NetworkSnapshot } from '../services/network-monitor';

export interface AdaptiveImageConfig {
  containerEl: HTMLElement;
  lowResSrc: string;    // WebP 480p (~25KB)
  highResSrc: string;   // AVIF 1440p (~220KB)
  altText: string;
}

export function mountAdaptiveImage(
  config: AdaptiveImageConfig,
  monitor: NetworkMonitor
): () => void {
  const img = document.createElement('img');
  img.alt = config.altText;
  img.loading = 'lazy';
  img.className = 'w-full h-auto transition-opacity duration-300';

  config.containerEl.appendChild(img);

  const unsubscribe = monitor.subscribe((snapshot: NetworkSnapshot) => {
    // Condition: Downgrade image if user has Save-Data enabled or network is slow
    const shouldSaveData = snapshot.saveData || 
      snapshot.effectiveType === 'slow-2g' || 
      snapshot.effectiveType === '2g' || 
      snapshot.effectiveType === '3g' ||
      (snapshot.downlinkMbps !== null && snapshot.downlinkMbps < 1.5);

    const targetSrc = shouldSaveData ? config.lowResSrc : config.highResSrc;

    if (img.src !== targetSrc) {
      img.src = targetSrc;
      console.log(`[Adaptive Loader] Selected: ${shouldSaveData ? 'Low-Res (Economy)' : 'High-Res (HD)'}`);
    }
  });

  return () => {
    unsubscribe();
    img.remove();
  };
}

Step 4: Handling Offline Queueing and Reconnection

When mobile users enter elevators or subways, network connections sever completely. Rather than presenting generic error modals that destroy user input, tie your network monitor to an offline mutation queue stored in IndexedDB.

  • Optimistic Updates: When offline, write records to local IndexedDB and update the DOM immediately with an amber "Pending Sync" indicator.
  • Reconnection Flushing: As soon as the network monitor detects transition back to online status with effectiveType === '4g', dispatch a background worker to replay queued requests with exponential backoff.

Testing Network Adaptations in Developer Tools

You do not need to walk into a basement to test how your application handles spotty coverage:

  1. Open Google Chrome DevTools (F12 or Cmd+Option+I).
  2. Switch to the Network tab.
  3. In the throttling dropdown (labeled No throttling by default), choose Fast 3G, Slow 3G, or Offline.
  4. Observe how your NetworkMonitor detects the synthetic RTT and downlink limits, swaps your media assets, and updates UI state instantly.

By shifting from naive 5G assumptions to disciplined, adaptive network programming, you construct resilient web applications that load quickly across global network conditions.

Found this useful?
View all articles

Keep Reading

Related Articles

Learn with Dropout Developer

Build real software with AI

Step-by-step learning paths, vibe coding tutorials, and certified developer programs designed for the modern engineer.