Adaptive Loading: Optimize for User's Device and Network
Adaptive loading ensures all users get an optimal experience regardless of their device or network conditions.
Quick Navigation: Network Information API • Device Capabilities Detection • Adaptive Image Loading • Adaptive Code Splitting • Save Data Mode • Best Practices
Quick Decision Guide
Quick Implementation Guide:
Network detection: Use navigator.connection.effectiveType to detect 4G/3G/2G and serve appropriate content.
Save Data mode: Check navigator.connection.saveData to respect user preference for reduced data usage.
Device capabilities: Use navigator.deviceMemory and navigator.hardwareConcurrency to detect device performance.
Adaptive images: Serve low-res on slow networks, high-res on fast. Use srcset or dynamic loading.
Best Practice: Default to low-quality, progressively enhance for better connections. Always respect save-data preference.
Network Information API
Detecting Connection Type
// Get connection info
const connection = navigator.connection
|| navigator.mozConnection
|| navigator.webkitConnection;
if (connection) {
console.log('Effective type:', connection.effectiveType);
// Returns: '4g', '3g', '2g', 'slow-2g'
console.log('Downlink speed:', connection.downlink);
// Returns: Mbps estimate (e.g., 10.5)
console.log('RTT:', connection.rtt);
// Returns: Round-trip time in ms (e.g., 50)
console.log('Save data:', connection.saveData);
// Returns: true if user enabled data saver
}React Hook
function useNetworkStatus() {
const [networkStatus, setNetworkStatus] = useState({
effectiveType: '4g',
downlink: 10,
saveData: false
});
useEffect(() => {
const connection = navigator.connection;
if (!connection) return;
const updateNetworkStatus = () => {
setNetworkStatus({
effectiveType: connection.effectiveType,
downlink: connection.downlink,
saveData: connection.saveData
});
};
updateNetworkStatus();
connection.addEventListener('change', updateNetworkStatus);
return () => {
connection.removeEventListener('change', updateNetworkStatus);
};
}, []);
return networkStatus;
}Usage
function App() {
const { effectiveType, saveData } = useNetworkStatus();
// Adapt based on network
const shouldLoadHighQuality =
effectiveType === '4g' && !saveData;
return (
<Image
src={shouldLoadHighQuality ? 'high-res.jpg' : 'low-res.jpg'}
/>
);
}Device Capabilities Detection
Memory Detection
// Device RAM (GB)
const memory = navigator.deviceMemory || 4;
console.log(`Device has ${memory}GB RAM`);
// Categorize device
function getDeviceTier() {
if (memory >= 8) return 'high-end';
if (memory >= 4) return 'mid-range';
return 'low-end';
}
const tier = getDeviceTier();CPU Detection
// Number of logical processors
const cores = navigator.hardwareConcurrency || 2;
console.log(`Device has ${cores} CPU cores`);
// Adjust thread pool
const maxWorkers = Math.max(1, cores - 1);Device Tier Hook
function useDeviceCapabilities() {
const memory = navigator.deviceMemory || 4;
const cores = navigator.hardwareConcurrency || 2;
const tier = useMemo(() => {
if (memory >= 8 && cores >= 4) return 'high';
if (memory >= 4 && cores >= 2) return 'medium';
return 'low';
}, [memory, cores]);
return {
memory,
cores,
tier,
isLowEnd: tier === 'low',
isHighEnd: tier === 'high'
};
}Adapt Features
function Dashboard() {
const { isLowEnd } = useDeviceCapabilities();
return (
<div>
<DataTable
enableVirtualization={!isLowEnd}
enableAnimations={!isLowEnd}
/>
{!isLowEnd && <HeavyChart />}
</div>
);
}Adaptive Image Loading
Network-Based Quality
function AdaptiveImage({ src, alt }) {
const { effectiveType, saveData } = useNetworkStatus();
const getImageQuality = () => {
if (saveData) return 'low';
switch (effectiveType) {
case '4g': return 'high';
case '3g': return 'medium';
case '2g':
case 'slow-2g':
default:
return 'low';
}
};
const quality = getImageQuality();
const imageSrc = `${src}?quality=${quality}`;
return <img src={imageSrc} alt={alt} loading="lazy" />;
}Progressive Image Loading
function ProgressiveImage({ lowRes, highRes, alt }) {
const [currentSrc, setCurrentSrc] = useState(lowRes);
const { effectiveType } = useNetworkStatus();
useEffect(() => {
// Only load high-res on good connection
if (effectiveType === '4g') {
const img = new Image();
img.src = highRes;
img.onload = () => setCurrentSrc(highRes);
}
}, [effectiveType, highRes]);
return (
<img
src={currentSrc}
alt={alt}
style={{
filter: currentSrc === lowRes ? 'blur(5px)' : 'none',
transition: 'filter 0.3s'
}}
/>
);
}Responsive Images with Network Hints
function ResponsiveImage() {
const { effectiveType } = useNetworkStatus();
// Skip high DPI on slow networks
const multiplier = effectiveType === '4g' ? 2 : 1;
return (
<img
srcSet={`
image-400w.jpg ${400 * multiplier}w,
image-800w.jpg ${800 * multiplier}w,
image-1200w.jpg ${1200 * multiplier}w
`}
sizes="(max-width: 600px) 400px, 800px"
src="image-800w.jpg"
alt="Responsive image"
/>
);
}Adaptive Code Splitting
Load Based on Connection
function AdaptiveComponent() {
const { effectiveType } = useNetworkStatus();
const { isLowEnd } = useDeviceCapabilities();
// Only load heavy features on good devices + fast network
if (effectiveType === '4g' && !isLowEnd) {
const HeavyFeature = lazy(() => import('./HeavyFeature'));
return (
<Suspense fallback={<Loading />}>
<HeavyFeature />
</Suspense>
);
}
// Lightweight alternative
return <LightweightFeature />;
}Adaptive Prefetching
function useAdaptivePrefetch() {
const { effectiveType, saveData } = useNetworkStatus();
const shouldPrefetch = useMemo(() => {
// Don't prefetch on slow networks or save-data mode
return effectiveType === '4g' && !saveData;
}, [effectiveType, saveData]);
const prefetchRoute = useCallback((route) => {
if (shouldPrefetch) {
import('./pages/' + route);
}
}, [shouldPrefetch]);
return prefetchRoute;
}
// Usage
function Navigation() {
const prefetch = useAdaptivePrefetch();
return (
<Link
to="/dashboard"
onMouseEnter={() => prefetch('dashboard')}
>
Dashboard
</Link>
);
}Adaptive Bundle Loading
async function loadAppropriateBundle() {
const { memory, cores } = await getDeviceCapabilities();
if (memory >= 8 && cores >= 4) {
// High-end: Load full features
return import('./bundles/full');
} else if (memory >= 4) {
// Mid-range: Load core features
return import('./bundles/core');
} else {
// Low-end: Load minimal features
return import('./bundles/minimal');
}
}Save Data Mode
Respecting User Preference
function useSaveData() {
const [saveData, setSaveData] = useState(false);
useEffect(() => {
const connection = navigator.connection;
if (!connection) return;
setSaveData(connection.saveData);
const handleChange = () => {
setSaveData(connection.saveData);
};
connection.addEventListener('change', handleChange);
return () => {
connection.removeEventListener('change', handleChange);
};
}, []);
return saveData;
}Adapt Content
function VideoPlayer({ videoUrl }) {
const saveData = useSaveData();
if (saveData) {
// Show thumbnail with play button
return (
<div onClick={playVideo}>
<img src="thumbnail.jpg" alt="Video thumbnail" />
<PlayIcon />
<p>Tap to play (Save Data is on)</p>
</div>
);
}
// Auto-play on good connection
return <video src={videoUrl} autoPlay />;
}Server-Side Hints
// Express middleware
app.use((req, res, next) => {
const saveData = req.headers['save-data'] === 'on';
req.saveData = saveData;
next();
});
// Serve appropriate images
app.get('/image', (req, res) => {
if (req.saveData) {
res.sendFile('image-low-quality.jpg');
} else {
res.sendFile('image-high-quality.jpg');
}
});Best Practices
Progressive Enhancement
// Start with baseline experience
function App() {
const [experience, setExperience] = useState('baseline');
const { effectiveType } = useNetworkStatus();
const { tier } = useDeviceCapabilities();
useEffect(() => {
// Progressively enhance based on capabilities
if (effectiveType === '4g' && tier === 'high') {
setExperience('premium');
} else if (effectiveType === '3g' && tier === 'medium') {
setExperience('standard');
}
// else stay at 'baseline'
}, [effectiveType, tier]);
return (
<div>
{experience === 'premium' && <PremiumFeatures />}
{experience === 'standard' && <StandardFeatures />}
<BaselineFeatures />
</div>
);
}Adaptive Loading Strategy
class AdaptiveLoader {
constructor() {
this.connection = navigator.connection;
this.memory = navigator.deviceMemory || 4;
this.cores = navigator.hardwareConcurrency || 2;
}
shouldLoadFeature(feature) {
const { effectiveType, saveData } = this.connection;
// Critical features: always load
if (feature.critical) return true;
// Respect save-data mode
if (saveData && !feature.essential) return false;
// Check network
if (effectiveType === 'slow-2g' || effectiveType === '2g') {
return feature.essential;
}
// Check device capability
if (this.memory < 4 && feature.memoryIntensive) {
return false;
}
return true;
}
getImageQuality() {
const { effectiveType, saveData } = this.connection;
if (saveData) return 0.3;
const qualityMap = {
'4g': 0.9,
'3g': 0.6,
'2g': 0.3,
'slow-2g': 0.2
};
return qualityMap[effectiveType] || 0.5;
}
}Testing
// Chrome DevTools: Throttle network
// Settings → Network → Add custom profile
// Simulate low memory
Object.defineProperty(navigator, 'deviceMemory', {
value: 2,
writable: false
});
// Simulate save-data
Object.defineProperty(navigator.connection, 'saveData', {
value: true,
writable: false
});