🕮8 min read · 1,447 words
Web performance in India is a different problem than web performance in the US or Europe. When you’re optimizing for a user on a 4G connection in Bhopal on a mid-range Android phone, the numbers that matter and the fixes that help are not the same as optimizing for someone on fiber in San Francisco.
This checklist is built specifically for Indian developers building for Indian users.
Why Indian Web Performance Is Different
Three factors make India a uniquely challenging performance environment:
Network variability. Indian mobile networks are fast when they’re good (5G in metro areas can be excellent) and extremely slow when they’re not (rural 2G/3G, peak hour congestion, elevator / basement dead zones). Your site needs to work on both — not just the good case.
Device diversity. The Indian smartphone market spans from flagship iPhones in corporate offices to ₹8,000 Android phones in tier-3 cities. JavaScript-heavy sites that perform beautifully on a high-end device can be completely unusable on low-end hardware.
High latency to Western servers. If your server is in the US or Europe, Indian users are dealing with 150–400ms base latency before your application even starts. This compounds every other performance problem.
The Checklist
Section 1 — Measure First
Never optimize what you haven’t measured. Start here:
| Tool | What It Measures | India-Specific Tip |
|---|---|---|
| PageSpeed Insights | Core Web Vitals, opportunities | Check mobile score, not just desktop |
| WebPageTest | Waterfall, real device testing | Test from Mumbai location, on mobile |
| GTmetrix | Waterfall, filmstrip | Set test location to Mumbai |
| Chrome DevTools | Network throttling simulation | Test on “Slow 4G” and “3G” presets |
| Lighthouse | Full audit with recommendations | Run in incognito, check mobile |
India baseline targets:
- LCP (Largest Contentful Paint): under 2.5 seconds on mobile
- FID / INP (Interaction to Next Paint): under 200ms
- CLS (Cumulative Layout Shift): under 0.1
- Time to First Byte (TTFB): under 800ms
- Total page weight: under 1MB for initial load
Section 2 — Server and Hosting
☐ Use a server or CDN with an India edge node
Cloudflare (free), AWS Mumbai (ap-south-1), DigitalOcean Bangalore, or Vercel — all have India-region infrastructure. Never serve Indian users from US-East if you can help it.
☐ Enable HTTP/2 or HTTP/3
HTTP/2 multiplexes multiple requests over one connection — critical for performance on high-latency connections. Check in Chrome DevTools → Network → Protocol column. Should show “h2” or “h3”.
☐ Set proper cache headers on static assets
# Nginx — cache static assets for 1 year
location ~* \.(js|css|png|jpg|webp|woff2|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
☐ Enable Gzip/Brotli compression
# Nginx — enable Brotli (better than Gzip)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json image/svg+xml;
☐ Implement server-side caching
For Laravel: Redis page caching via Laravel Cache or Nginx FastCGI cache. A page that takes 800ms to generate from the database should serve in 5ms from cache.
Section 3 — Images (Biggest Win for Most Indian Sites)
☐ Convert all images to WebP or AVIF
WebP is 30–40% smaller than JPEG at the same quality. AVIF is another 20% smaller than WebP. Both are supported on all modern browsers.
// In HTML — serve WebP with JPEG fallback
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image" loading="lazy">
</picture>
// In Laravel — auto-convert on upload
use Intervention\Image\Laravel\Facades\Image;
$image = Image::read($request->file('image'));
$image->toWebp(80)->save(storage_path('app/public/images/' . $filename . '.webp'));
☐ Serve correctly sized images
A 2000px wide image displayed at 400px width wastes 80% of the bandwidth. Use responsive images:
<img
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px"
src="hero-800.webp"
alt="Hero"
loading="lazy"
>
☐ Lazy load all below-the-fold images
<img src="product.webp" loading="lazy" alt="Product">
☐ Set explicit width and height on all images
This prevents layout shift (CLS) — one of the most common CWV failures:
<img src="product.webp" width="400" height="300" loading="lazy" alt="Product">
☐ Compress images before upload
Target: product images under 100KB, hero images under 200KB, thumbnails under 30KB. Tools: Squoosh (browser), ImageOptim (Mac), Cloudflare Images (automated).
Section 4 — JavaScript
☐ Audit your JavaScript bundle size
# Vite — analyze bundle
npm run build -- --analyze
# Or install the visualizer plugin
npm install --save-dev rollup-plugin-visualizer
☐ Remove unused JavaScript
Every npm package you install adds to your bundle. Audit with:
npx depcheck # Find unused packages
npx bundlephobia # Check package sizes before installing
☐ Code split at the route level
Don’t load the entire application upfront. Load only the code for the current page:
// React — lazy load route components
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
// In router
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
☐ Defer non-critical JavaScript
<!-- Load after HTML parsed -->
<script src="analytics.js" defer></script>
<!-- Load when browser is idle -->
<script>
window.addEventListener('load', () => {
import('./chat-widget.js');
});
</script>
Section 5 — CSS
☐ Remove unused CSS
Tailwind CSS already purges unused classes in production. For custom CSS, use PurgeCSS.
☐ Inline critical CSS
The CSS needed to render above-the-fold content should be inlined in the HTML head — not loaded from a separate file:
<!-- Inline critical CSS -->
<style>
/* Only the CSS needed for above-the-fold content */
body { margin: 0; font-family: Inter, sans-serif; }
.hero { ... }
</style>
<!-- Load the rest asynchronously -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
Section 6 — Fonts
☐ Use font-display: swap
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap; /* Show fallback immediately, swap when loaded */
}
☐ Self-host fonts instead of Google Fonts
Google Fonts adds a DNS lookup, a connection, and a download from Google’s servers. Self-hosting eliminates this. Use Google Webfonts Helper to download and self-host any Google Font.
☐ Preconnect to font providers if you must use CDN
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
☐ Only load the weights you use
<!-- Don't load all weights -->
<!-- fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900 -->
<!-- Load only what you use -->
<!-- fonts.googleapis.com/css2?family=Inter:wght@400;600;700 -->
Section 7 — India-Specific Optimizations
☐ Design for offline / poor connectivity
Use Service Workers to cache the application shell. Users on spotty connections should see content, not blank screens or spinners.
// service-worker.js — cache app shell
const CACHE_NAME = 'app-shell-v1';
const SHELL_URLS = ['/', '/css/app.css', '/js/app.js', '/offline.html'];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(SHELL_URLS))
);
});
☐ Test on real Indian network conditions
In Chrome DevTools → Network → Throttling, test on “Slow 4G” (which represents realistic Indian 4G outside metro areas). If your site is unusable on Slow 4G, a significant chunk of Indian users can’t use it.
☐ Optimize for Android mid-range devices
Test on or emulate a device with 2GB RAM and a mid-range CPU. Heavy JavaScript will cause visible jank on these devices. Use Chrome’s CPU throttling (6x slowdown) to simulate this in DevTools.
☐ Use Cloudflare in front of your site
Cloudflare’s free plan gives you a global CDN, DDoS protection, and Brotli compression automatically. For Indian users, Cloudflare routes traffic through its Mumbai and Chennai nodes — reducing latency significantly compared to serving from a single region VPS.
Section 8 — Monitoring
☐ Set up Real User Monitoring (RUM)
Synthetic tests (PageSpeed, WebPageTest) show you your site’s performance in ideal conditions. RUM shows you what real users actually experience. Cloudflare Web Analytics (free), Google Search Console’s CWV report, or web-vitals.js are all good options.
// web-vitals.js — measure real user performance
import { onCLS, onFID, onLCP, onTTFB, onINP } from 'web-vitals';
function sendToAnalytics(metric) {
// Send to your analytics endpoint
fetch('/analytics/vitals', {
method: 'POST',
body: JSON.stringify(metric),
});
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
onINP(sendToAnalytics);
☐ Set up alerts for performance regressions
Add a Lighthouse CI check to your GitHub Actions pipeline that fails the build if Core Web Vitals drop below your thresholds:
# .github/workflows/lighthouse.yml
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://yoursite.com
budgetPath: ./lighthouse-budget.json
Performance Budget for Indian Sites
| Resource | Budget |
|---|---|
| Total page weight (initial) | < 1MB |
| Images (initial load) | < 500KB |
| JavaScript (initial) | < 200KB gzipped |
| CSS (initial) | < 50KB gzipped |
| Fonts | < 100KB total |
| Third-party scripts | < 100KB |
| Time to Interactive | < 5 seconds (Slow 4G) |
If you need a performance audit for your site or want help implementing these optimizations, our team at Softcrony has done this for dozens of Indian businesses.
Leave a comment