| name | network-efficiency |
| description | Cut mobile network cost with HTTP/2, HTTP/3 (QUIC), Brotli/Zstd compression, and payload trimming. Use when p95 latency or bytes-per-screen exceeds budget. |
Network Efficiency
Instructions
Mobile networks are slow, lossy, and expensive. Every request pays connection setup, every byte pays radio time. Getting network efficiency right is often the single biggest perf win.
1. Budgets
| Metric | Budget (p95) |
|---|
| Bytes per screen | ≤ 150 KB (excl. images) |
| Image bytes per screen | ≤ 500 KB |
| Time-to-first-byte | ≤ 400 ms (4G) |
| Requests per screen | ≤ 8 |
2. Use HTTP/2 and HTTP/3
HTTP/1.1 means one in-flight request per connection; six connections to the same host become the ceiling. HTTP/2 multiplexes; HTTP/3 (QUIC over UDP) survives IP changes — critical on mobile as users move between Wi-Fi and cellular.
- Ensure your CDN advertises HTTP/3 via Alt-Svc.
- Android: OkHttp supports HTTP/2 by default; HTTP/3 via Cronet or
okhttp-3-quic (experimental).
- iOS:
URLSession uses HTTP/3 when server advertises it (iOS 15+).
- RN: use
fetch backed by URLSession/OkHttp — HTTP/2 is transparent. Consider react-native-netinfo to detect network type.
OkHttp with HTTP/2 preserved:
val client = OkHttpClient.Builder()
.protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
.connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))
.connectTimeout(10, TimeUnit.SECONDS)
.callTimeout(20, TimeUnit.SECONDS)
.build()
3. Compression
Always request and accept compressed responses. Brotli beats gzip by 15–25% on JSON; Zstd is competitive and widely deployed in 2026.
Accept-Encoding: br, zstd, gzip
- OkHttp: Brotli support via
BrotliInterceptor.
- URLSession: gzip built-in; Brotli requires a custom decoder.
- Server: enable
br at the CDN edge for JSON APIs.
4. Trim Payloads
Shave bytes before you compress them:
- Avoid over-fetching. Use GraphQL or server-side field selection (
?fields=id,title,thumb).
- Use short JSON keys (
{"t":"..."} vs {"title":"..."}) — gains compound after Brotli.
- Use MessagePack, Protobuf, or CBOR for latency-critical APIs. Protobuf parses ~5× faster than JSON on mobile.
- Strip base64-encoded fields; fetch binaries as binaries.
- Move heavy fields behind
include= flags; fetch only on detail screens.
5. Keep Connections Warm
-
Use a shared OkHttpClient / URLSession instance for the app's lifetime. Connection pools + DNS cache are instance-scoped.
-
Preconnect to critical hosts at app start (after first frame):
Android OkHttp:
client.dispatcher.executorService.execute {
Request.Builder().url("https://api.example.com/ping").head().build().let { req ->
runCatching { client.newCall(req).execute().close() }
}
}
iOS: warm by creating the session and doing a zero-cost HEAD / nw_establishment_report.
6. Caching Headers
Make caching declarative at the edge; the client library honors standard headers automatically.
Cache-Control: public, max-age=60, stale-while-revalidate=600, stale-if-error=86400
ETag: "abc123"
Vary: Accept, Accept-Encoding
stale-while-revalidate is perfect for feeds — serve instantly, refresh in background.
- See
request-batching-and-caching skill for client-side patterns.
7. Image-Specific Tactics
- CDN responsive sizes:
/img/abc.jpg?w=720&q=80&fm=avif.
- Prefer AVIF/WebP; see
bitmap-and-image-optimization.
- Use
loading=lazy equivalents in RN (FastImage priority="low").
- See
image-delivery skill.
8. Avoid Common Footguns
- Do not disable HTTPS or pin weakly. Mobile carriers rewrite HTTP responses (injected ads, GZIP stripping) — HTTPS is also a perf feature.
- Do not bundle request and response interceptors that decompress twice.
- Do not serialize large objects on the UI thread.
Gson.toJson(list) on main is a classic jank cause.
- Do not send timestamp as strings when you use them; sending epoch millis avoids parse cost.
- Do not send analytics events one-by-one over the wire; batch (see
background-work-optimization).
9. Measure
- Android: Profiler → Network view shows bytes, request/response timing. OkHttp
EventListener logs DNS, connect, TLS, response.
- iOS: Instruments Network template;
URLSession taskMetrics (URLSessionTaskMetrics) — sum DNS, connect, TLS, wait, receive.
- Flutter: DevTools → Network tab.
- RN: Flipper → Network plugin; Metro network inspector.
func urlSession(_ s: URLSession, task: URLSessionTask, didFinishCollecting m: URLSessionTaskMetrics) {
m.transactionMetrics.forEach { t in
let secure = t.negotiatedTLSProtocolVersion != nil
}
}
Checklist