Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
PerformanceJune 27, 20264 min read

Client Hints for Adaptive Loading: A Modern Performance Guide

Learn how to use Client Hints and Accept-CH for adaptive loading. Optimize your assets based on device capability to improve Core Web Vitals and speed.

PerformanceWeb PerformanceClient HintsWeb DevelopmentCore Web VitalsAdaptive LoadingWeb VitalsFrontend

Last month, I spent about three days debugging a massive LCP (Largest Contentful Paint) regression that only seemed to hit users on mid-tier Android devices. After digging through our logs, I realized we were shipping high-resolution 4K hero images to users who couldn't even render them, wasting bandwidth and blocking critical CSS.

If you’re still relying on brittle user-agent sniffing to serve assets, you’re likely over-delivering data to mobile users or under-serving your high-end desktop traffic. Client Hints offer a cleaner, more robust way to handle content negotiation by letting the browser explicitly tell the server what it needs.

Why Move Beyond User-Agent Sniffing?

User-Agent (UA) strings are a mess. They are historically bloated, unreliable, and often intentionally spoofed by browsers to maintain compatibility. Trying to parse a UA string to guess a device’s memory, screen width, or network speed is a losing game.

Instead, we want to move toward Adaptive Loading where the server knows exactly what the client can handle before it even sends the first byte of the image or script. When I implemented Client Hints, I saw our image payload size drop by roughly 1.8x for mobile users, which directly improved our LCP scores.

Implementing Accept-CH and Client Hints

To get started, your server needs to signal which "hints" it wants the browser to send. You do this using the Accept-CH response header.

In your Nginx or Node.js middleware, add the following header:

HTTP
Accept-CH: Sec-CH-Viewport-Width, Sec-CH-DPR, Sec-CH-Width, Sec-CH-Device-Memory

Once the browser sees this on the initial request, it will include these values as headers in subsequent requests. It’s important to note that for the very first request, the browser won't have these hints yet. This is where a bit of architecture planning comes in.

The Trade-off: The "First Request" Problem

We initially tried to handle everything at the edge using Cloudflare Workers. It worked well, but we hit a snag: if the browser doesn't have the hints stored, you have to provide sensible defaults.

FeatureUA SniffingClient Hints
AccuracyLowHigh
MaintenanceHigh (regex hell)Low (declarative)
PerformancePoorExcellent
PrivacyLowHigh (Reduced UA)

If you ignore the "first request" gap, you might end up with a layout shift because the server sent the wrong image size before it knew the viewport width. We solved this by using a small, base-level image and then triggering a re-fetch or using srcset with the hint-provided data. If you’re struggling with similar layout issues, you might want to look into Adaptive Loading Strategies: Building Self-Healing Web Performance to handle those edge cases gracefully.

Putting It Into Practice

Once your server receives these headers, you can use them to build dynamic URLs. For example, if you see Sec-CH-DPR: 2.0, you know to serve a 2x image.

JAVASCRIPT
// Example in a Node.js/Express environment
app.use((req, res, next) => {
  const dpr = req.get(CE9178">'Sec-CH-DPR') || 1.0;
  const viewportWidth = req.get(CE9178">'Sec-CH-Viewport-Width') || 390;
  
  // Logic to select image size from your CDN
  req.imageVariant = selectBestVariant(dpr, viewportWidth);
  next();
});

This approach keeps your logic decoupled from the browser version. It’s significantly easier to maintain than a massive library of device profiles.

Connecting Performance to Core Web Vitals

The goal here isn't just "cleaner code"—it's Performance Optimization. By ensuring that we don't fetch heavy resources unnecessarily, we reduce the total bytes downloaded. This helps keep the main thread free for critical tasks.

If you find that your backend is still the bottleneck despite these frontend optimizations, it’s worth investigating the Server-Timing API for INP Optimization: Debugging Backend Latency. Sometimes, what looks like a frontend performance issue is actually the server taking too long to generate the response that includes these hints.

A Note on Critical Resources

Don't use this for everything. Prioritize your hero images, critical fonts, and major JS bundles. If you try to optimize every single icon or small asset, you’ll end up with cache fragmentation on your CDN. Remember that every unique set of headers creates a new cache key.

We also found that combining this with Resource Prioritization Strategies: Using Fetch Priority for Speed gave us the best results. We used Client Hints to pick the right file, and Fetch Priority to ensure the browser downloaded it immediately.

Final Thoughts

Is this perfect? No. You still have to deal with browsers that don't support specific hints and the initial request latency. I’m still experimenting with Critical-CH (the Critical-CH header) to force a browser to retry a request if the hints are missing, but it comes with a performance penalty.

If I were starting this again, I’d spend more time auditing our CDN cache hit rates before rolling out the headers. It’s easy to accidentally destroy your cache efficiency if you aren't careful with how your origin server varies content. Start small, measure the impact on your real-world users, and don't assume every hint will be available in every request.

Back to Blog

Similar Posts

PerformanceJune 28, 20264 min read

Core Web Vitals Optimization: Eliminating Critical Request Chains

Learn to eliminate critical request chains and boost Core Web Vitals. Discover how precise resource prioritization and preload scanning improve load times.

Read more
PerformanceJune 28, 20263 min read

Document Policy and Early Hints: Mastering Critical Path Latency

Master Document Policy and Early Hints to slash critical path latency. Learn how to control browser network scheduling and optimize your resource prioritization.

Read more
PerformanceJune 27, 20264 min read

Adaptive Loading: Mastering Client Hints and Network Information API

Adaptive Loading using Client Hints and the Network Information API helps you deliver faster experiences. Learn how to tailor assets to real user conditions.

Read more