Making your website fast on Indian mobile networks (4G and slower)
10 min read · 25-Feb-2025
villagehosting.in team
25 February 2025
India has the cheapest mobile data in the world and some of the most impatient internet users. On a Jio 4G connection in a tier-2 city, a site that takes 5 seconds to load loses the visitor. Here is how to optimise for real Indian conditions.
Test on a real mid-range Android device
High-end iPhones and flagships make any site look fast. The median Indian mobile user is on a device with 3–4 GB RAM and a mid-range processor that's 3–5 years old. Test on a real Redmi or Moto G device, not Chrome DevTools device emulation — CPU throttling in DevTools is not accurate enough for real-world Indian mobile performance.
The real Indian mobile experience
Your site may look fast on your office WiFi or even your 5G phone in a metro. But test it on:
- A mid-range Android phone (₹8,000–₹15,000)
- Jio 4G in a town outside the top-5 cities
- Or simulate it: Google Chrome DevTools → Network → "Slow 4G"
This is a significant portion of your actual audience. Design for them, not for your MacBook.
What actually matters
The single most impactful thing: how quickly does the user see something useful?
Largest Contentful Paint (LCP) — the largest element visible to the user, usually your hero image or headline. Must be under 2.5 seconds.
First Contentful Paint (FCP) — when the browser first renders any content. Should be under 1.8 seconds.
Everything else is secondary. Fix LCP first.
Fix 1: Optimise your hero image
The hero image is usually the LCP element and the single biggest culprit.
Convert to WebP: WebP images are 25–35% smaller than JPEG at the same quality. Use:
- Squoosh (squoosh.app) — free, runs in browser
- TinyPNG/TinyJPG — free API, WordPress plugin available
- WP Smush or ShortPixel plugins for automatic conversion
Target file size: Under 100KB for hero images. Under 50KB for most images.
Set explicit dimensions:
<img src="hero.webp" width="1200" height="600" alt="..." />
Prevents layout shift (CLS) and helps the browser allocate space before the image loads.
Preload your LCP image:
<link rel="preload" as="image" href="hero.webp" />
Add this in the <head>. It tells the browser to fetch the image immediately, before it parses the HTML.
Do not lazy-load the LCP image: Lazy loading defers images below the fold. Your hero image is above the fold — lazy loading it delays LCP. Exclude it from lazy loading.
Fix 2: Serve images at the right size
Sending a 2000px wide image to a mobile phone with a 400px screen is wasteful. Use srcset to serve different sizes:
<img
src="hero-800.webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="..."
/>
For WordPress, modern themes handle this automatically. For custom sites, generate multiple image sizes during the build process.
Fix 3: Fix your server response time (TTFB)
If your Time to First Byte (TTFB) is over 600ms, you have a server problem that no amount of frontend optimisation will fix. Fix the server first.
On shared hosting:
- Enable PHP OPcache (cPanel → MultiPHP INI Editor → opcache.enable = 1)
- Enable a caching plugin (LiteSpeed Cache on LiteSpeed hosting)
- Check if your hosting plan is overloaded (TTFB consistently > 1s means it is time to upgrade)
On VPS:
- Enable Redis object caching
- Ensure NGINX/LiteSpeed is properly configured
- Check slow query logs for database bottlenecks
Fix 4: Eliminate render-blocking resources
CSS and JavaScript in the <head> block the browser from rendering the page. Every millisecond spent loading an unused stylesheet is a millisecond the user sees a blank screen.
CSS: Load only the CSS needed for above-the-fold content inline. Defer the rest.
WordPress plugins: WP Rocket's "Remove Unused CSS" or LiteSpeed Cache's CSS Combine.
JavaScript:
Add defer or async to non-critical scripts:
<script src="analytics.js" defer></script>
defer — loads the script after HTML is parsed (safe for most scripts)
async — loads in parallel, executes immediately when done (for scripts with no dependencies)
Fix 5: Lazy load images below the fold
Every image that is not immediately visible when the page loads should be lazy-loaded:
<img src="product.webp" loading="lazy" alt="..." />
All modern browsers support the loading="lazy" attribute. For WordPress, enable lazy loading in your caching plugin or use the a3 Lazy Load plugin.
Fix 6: Remove heavy third-party scripts
Third-party scripts (chat widgets, marketing pixels, customer feedback tools) are common culprits. Each one:
- Adds a DNS lookup
- Makes a network request to an external server
- Often blocks rendering
- Can be hosted on a slow server
Audit your third-party scripts: Open Chrome DevTools → Network → filter by third-party domains. Identify what is taking longest.
Google Tag Manager is not a solution for this. GTM adds one script but can still load dozens of scripts through it, with all their associated performance costs.
Alternatives:
- Replace chat widgets with a simple WhatsApp button (one SVG image link)
- Replace heavy analytics with Plausible (1.4KB script vs 80KB+ for Google Analytics 4)
- Load Facebook Pixel with
deferand trigger it only when the user scrolls or interacts
Fix 7: Subset your web fonts
Google Fonts can add 100–400KB of font data. Subset them to include only the characters you need:
If you use a Latin + Devanagari or other Indian script:
Use the subset parameter in the Google Fonts URL:
https://fonts.googleapis.com/css2?family=Noto+Sans&subset=latin,devanagari
Use font-display: swap:
@font-face {
font-family: 'YourFont';
font-display: swap; /* Show fallback font immediately, swap when loaded */
}
This prevents text from being invisible while the font loads (FOIT — Flash of Invisible Text).
Fix 8: Enable browser caching
Returning visitors load static assets (images, CSS, JS) from their browser cache instead of downloading them again. Set long cache headers:
In .htaccess:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
</IfModule>
Or use Cloudflare — it sets cache headers automatically and serves assets from Indian edge nodes.
Testing on real Indian conditions
Chrome DevTools simulation:
- Open DevTools (F12)
- Click the Network tab
- Change "No throttling" to "Slow 4G"
- Reload your page
This simulates a real Indian 4G connection in a non-metro area.
Real-device testing: Buy a ₹8,000 Android phone (Redmi 12 or similar). Use it on a real SIM. Test your site. If it feels fast on that device, you have done well.
PageSpeed Insights field data: After you have real traffic, PageSpeed Insights shows "Field Data" — the real performance experience of your actual visitors, sampled from Chrome users. This is more accurate than any lab test.
Realistic targets for Indian mobile
Achievable with the above optimisations on properly-configured shared hosting:
- LCP < 2.5 seconds on Slow 4G
- FCP < 1.5 seconds
- Page weight under 1MB total
- TTFB under 800ms