Mobile speed problems almost always look like a design problem before anyone realizes they're an infrastructure problem. Conversion rate on mobile lags desktop, bounce rate creeps above 60–70%, and the design gets blamed first. But on a mid-range Android device over a 4G connection, it's common for a homepage to take four or five seconds to reach Largest Contentful Paint — and no amount of visual polish fixes that.
In almost every audit we run, the same three root causes show up, each contributing roughly equally to the total load time: an unoptimized image pipeline, render-blocking third-party scripts, and a cache strategy that's doing almost no work on repeat visits. Here's how to fix each one.
1. The image pipeline
The most common offender is full-resolution JPEGs served to every device, mobile included. A 4800×3200px hero shot delivered to a 390px-wide phone screen is routine, and after compression that single image can account for a megabyte or more of page weight on its own.
- Switch imagery to WebP with AVIF fallbacks — 40–65% size reduction versus JPEG at equivalent quality
- Implement responsive image srcsets so mobile devices receive images sized to the viewport
- Use a CDN-level image transformation pipeline (Cloudflare Images, Next.js Image, etc.) to generate sizes on demand rather than pre-generating every variant
- Add explicit width/height attributes to every image element to eliminate layout shift
- Lazy-load all images below the fold with the native loading='lazy' attribute
2. Render-blocking scripts
It's common to find six or more third-party scripts loading synchronously in the document head: analytics platforms, a chat widget, a cookie banner, an A/B testing library, a heatmap tool. Each one blocks the browser from parsing and rendering the page until it finishes loading — and most of them were added once and never revisited.
The fix starts with an audit of what's actually being used. It's common to find a chat widget with a fraction-of-a-percent engagement rate, a duplicate analytics platform, or a heatmap tool nobody has opened in months. Cutting dead scripts is usually worth more than optimizing the ones you keep.
- Move remaining scripts to defer or async loading
- Audit your tag manager and prune inactive tags
- Replace heavy cookie-banner libraries with a lightweight custom implementation
- Consider a server-side variant approach for A/B testing that adds zero client-side weight
3. Cache strategy
It's surprisingly common for cache headers to be set to Cache-Control: no-cache on virtually every asset. That means every page load makes full round-trip requests for CSS, JavaScript, and fonts that haven't changed in weeks. On a mobile network with 80ms+ latency, that's routinely 400–600ms of pure waste per visit.
Immutable static assets should have a cache lifetime of one year, not zero. A build hash in the filename handles cache busting — you don't need to disable caching to get fresh files.
- Static assets (JS bundles, CSS, fonts, images) set to Cache-Control: public, max-age=31536000, immutable
- HTML pages set to Cache-Control: public, max-age=0, must-revalidate with ETags enabled
- API responses cached at the edge with stale-while-revalidate where content allows it
- Service worker added for repeat visitors — critical assets served from cache on second load
What this adds up to
None of these three fixes is exotic on its own. What makes the difference is treating them as one problem instead of three separate tickets. A site that fixes only its images but keeps eight blocking scripts will barely move its LCP. A site that fixes all three usually sees mobile load time drop by more than half, and mobile bounce rate follow it down within a few weeks.
The lesson isn't that performance work is easy. It's that performance problems are almost always additive — they're the sum of many small decisions that each seemed reasonable at the time. Fixing them requires looking at the whole stack rather than optimizing individual elements in isolation.