1. Know Your Actual User Profile

Before you optimise, understand what you're optimising for. India's internet users look roughly like this:

78%
Mobile-only users
₹8K–15K
Dominant Android price range
25 Mbps
Median 4G speed (not peak)
250ms+
Typical 4G latency (non-metro)

That ₹12,000 Redmi Note has 3GB RAM, a Snapdragon 680 or similar, and runs a browser that's 2-3 versions behind Chrome. Your React SPA with 4MB of JavaScript will choke it.

2. The Metric That Actually Matters: LCP

Google's Core Web Vitals define three performance metrics, but for Indian contexts, Largest Contentful Paint (LCP) is king. LCP measures how long until the largest visible element (usually the hero image or main heading) is fully painted.

Google's threshold: <2.5s is "Good", 2.5–4s is "Needs Improvement", >4s is "Poor". On a fast Indian 4G connection, your LCP baseline is probably 3–6 seconds — which means you're already in the "Needs Improvement" zone before any of your users hit slow patches.

Test on realistic conditions

In Chrome DevTools → Network tab → throttle to "Slow 4G" (40 Mbps download, 40ms latency). In Lighthouse → set device to "Mobile", CPU throttling to "4x slowdown". This better approximates a real Tier-2 experience than testing on your fiber connection.

3. Images: The Biggest Win

Images are almost always 50-70% of total page weight. Attack them first:

  1. Switch to WebP/AVIF: A 400KB JPEG hero becomes a 180KB WebP and a 120KB AVIF. Use the <picture> element with fallbacks.
  2. Resize for mobile: Serving a 2400×1600 desktop image to a 390px-wide phone is criminal. Use srcset to serve a 800px image to mobile users.
  3. Lazy load everything below the fold: loading="lazy" is now supported in all major browsers and defers image loading until the user scrolls near them.
  4. Set explicit dimensions: Width and height attributes on <img> prevent layout shift as images load (also improves CLS metric).
<!-- ✅ Production-ready image with format fallback + lazy + sizing -->
<picture>
    <source
        srcset="hero-800.avif 800w, hero-1600.avif 1600w"
        sizes="(max-width: 768px) 100vw, 800px"
        type="image/avif">
    <source
        srcset="hero-800.webp 800w, hero-1600.webp 1600w"
        sizes="(max-width: 768px) 100vw, 800px"
        type="image/webp">
    <img
        src="hero-800.jpg"
        alt="Hero banner"
        width="800" height="450"
        loading="lazy"
        decoding="async">
</picture>

4. JavaScript: The Silent Killer on Mid-Range Phones

Downloading 2MB of JavaScript on a slow connection takes 8 seconds on "Slow 4G". But that's not the worst part — the browser then has to parse and compile all that JS before running it. On a Snapdragon 680, parsing 2MB of JS can take another 3-4 seconds of CPU time.

What to do:

  • Code splitting: Don't load your checkout code on the homepage. React's React.lazy() and import() let you load components on demand.
  • Tree shaking: Ensure your bundler eliminates unused exports. A common culprit: importing all of lodash when you only use debounce.
  • Audit your bundle: Use webpack-bundle-analyzer or vite-bundle-visualizer. You'll be surprised what's hiding in there (often a 400KB moment.js or an unneeded PDF library).
  • Defer non-critical JS: Analytics, chat widgets, social SDKs — load them with defer or async. They shouldn't block your hero render.
// ❌ Loads all routes upfront
import Dashboard from './pages/Dashboard';
import Checkout from './pages/Checkout';

// ✅ Each page is a separate chunk, loaded on demand
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Checkout  = React.lazy(() => import('./pages/Checkout'));

5. Fonts: The Invisible Bottleneck

Google Fonts is free and beautiful, but loading 4 font weights from Google's servers adds 2-4 extra DNS lookups and HTTP requests. Two improvements:

<!-- ✅ Preconnect to font origin (eliminates DNS lookup delay) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- Only load the weights you actually use -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">

For maximum performance: self-host fonts (download from fonts.google.com, host as woff2 on your CDN, use font-display: swap).

6. CDN Placement for India

If your origin server is in Singapore or US-East, Indian users face high latency. Serving from a CDN edge node that has points of presence in Mumbai, Chennai, or Delhi cuts TTFB (Time To First Byte) from 300-500ms to 20-50ms.

CloudFront (AWS) has PoPs in Mumbai, Chennai, Hyderabad, and Delhi. Cloudflare has extensive Indian coverage. If you're on AWS and your static site is served from a bucket in ap-south-1 (Mumbai), add CloudFront in front — it's inexpensive and often the single highest-impact performance change for Indian users.

7. Critical Rendering Path Checklist

Audit these before your next release:

CheckTarget
LCP (Largest Contentful Paint)<2.5s on Slow 4G
Total blocking time<300ms
Hero image formatWebP or AVIF
Hero image size<100KB (mobile)
Total JS (first load)<200KB (compressed)
Unused CSS<10% of CSS size
Google Fonts preconnectYes
CDN with Indian PoPYes
loading="lazy" on off-screen imagesYes
One change, biggest impact

If you do nothing else from this article: convert your hero and product images to WebP and add loading="lazy" to everything below the fold. This single change has reduced page weight by 40-60% for many Indian product teams, with zero effect on visual quality.

Base64 Encoder / Image Tools

Convert, inspect and encode images and text — useful for optimising assets and debugging performance issues in your web projects.

Open Base64 Tool