Web application performance directly impacts revenue. Google reports that a 1-second delay in page load reduces conversions by 7%. A 3-second load time increases bounce rate by 32% compared to a 1-second load time. This guide covers five performance layers: Core Web Vitals, browser caching, server-side caching, CDN strategy, and database query optimization. Each section includes measurable targets and tested configurations.
According to HTTP Archive data from March 2026, the median page weight for web applications is 2.4 MB with a Largest Contentful Paint (LCP) of 3.8 seconds on mobile. The top 10% of performing sites achieve LCP under 1.5 seconds. The difference is not hardware or bandwidth but architectural decisions that compound across every page load.
---
Core Web Vitals Targets
| Metric | Good | Needs Improvement | Poor |
| LCP (Largest Contentful Paint) | < 2.5s | 2.5s - 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | < 200ms | 200ms - 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | < 0.1 | 0.1 - 0.25 | > 0.25 |
LCP Optimization Checklist
<!-- Optimal LCP image loading -->
<head>
<link rel="preload" as="image" href="/hero.avif" type="image/avif">
<style>/* Critical CSS inlined here */</style>
</head>
<body>
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="Hero image"
loading="eager" fetchpriority="high"
width="1200" height="628">
</picture>
</body>
INP Optimization
Interaction to Next Paint measures how quickly the page responds to user input. The primary causes of poor INP are long JavaScript tasks blocking the main thread.
- Break long tasks into smaller chunks using `requestIdleCallback` or `scheduler.yield()`
- Defer third-party scripts (analytics, chat widgets, tracking pixels)
- Use web workers for CPU-intensive computations
- Virtualize long lists (react-window or @tanstack/virtual)
- Debounce rapid-fire events (scroll, resize, input)
---
Multi-Layer Caching Strategy
Effective caching operates at four layers, each reducing load on the layer below it.
| Layer | Cache Type | TTL | Hit Rate Target |
| Browser | HTTP cache headers | 1 year (static), 0 (API) | 90%+ for static |
| CDN | Edge cache | 1 hour - 1 day | 80%+ for static |
| Application | Redis/Memcached | 5 min - 1 hour | 70%+ for read-heavy |
| Database | Query result cache | 1 min - 15 min | 50%+ |
HTTP Cache Headers
<h1>nginx cache configuration</h1>
server {
# Static assets: cache for 1 year (use content hashing in filenames)
location ~* \.(js
css png jpg jpeg gif ico svg woff2 avif
webp)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
# HTML: no cache (always fetch fresh)
location ~* \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# API responses: short cache for read endpoints
location /api/ {
add_header Cache-Control "private, max-age=0, must-revalidate";
}
}
Application-Level Caching with Redis
<h1>Python/FastAPI caching pattern</h1>
import redis
import json
from functools import wraps
redis_client = redis.Redis(host="localhost", port=6379, db=0)
def cache_response(ttl_seconds=300):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
cache_key = f"{func.__name__}:{hash(str(args) + str(kwargs))}"
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached)
result = await func(*args, **kwargs)
redis_client.setex(cache_key, ttl_seconds, json.dumps(result))
return result
return wrapper
return decorator
@cache_response(ttl_seconds=300)
async def get_dashboard_metrics(user_id: int):
# Expensive database query cached for 5 minutes
return await db.fetch_dashboard(user_id)
---
CDN Configuration
A CDN reduces latency by serving content from edge locations near users. For applications hosted on Arxentra, integrate a CDN for static assets while keeping API calls direct to the origin. See Services Overview for Arxentra CDN options.
CDN Best Practices
- Cache static assets at the edge with long TTLs
- Use cache-busting via content hashes in filenames (e.g., `app.a1b2c3.js`)
- Set up a custom domain on the CDN to avoid CORS issues
- Enable Brotli compression at the edge for 15-20% smaller payloads than gzip
- Configure cache purge webhooks in your CI/CD pipeline
---
Database Query Optimization
Database queries are the most common performance bottleneck in web applications. The 80/20 rule applies: 80% of query time comes from 20% of queries.
-- Find the slowest queries in PostgreSQL
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
-- Check for missing indexes
SELECT relname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch
FROM pg_stat_user_tables
WHERE seq_scan > 1000
ORDER BY seq_tup_read DESC;
-- Create targeted indexes based on slow query analysis
CREATE INDEX CONCURRENTLY idx_services_user_status
ON services (user_id, status)
WHERE status IN ('running', 'pending');
Query Optimization Rules
For PostgreSQL hosting with optimized defaults, see Pricing for managed database plans that include automated query analysis.
---
Load Testing Baseline
Every performance optimization should be validated with load testing. Use k6 for scriptable, developer-friendly load tests.
// k6-load-test.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 50 }, // ramp up
{ duration: "5m", target: 50 }, // sustained load
{ duration: "2m", target: 100 }, // peak
{ duration: "5m", target: 100 }, // sustained peak
{ duration: "2m", target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ["p(95)<500"], // 95th percentile under 500ms
http_req_failed: ["rate<0.01"], // less than 1% errors
},
};
export default function () {
const res = http.get("https://your-app.arxentra.cloud/api/health");
check(res, { "status is 200": (r) => r.status === 200 });
sleep(1);
}
---
Frequently Asked Questions
How do I measure Core Web Vitals in production?
Use the web-vitals JavaScript library to capture real user metrics (RUM) and send them to your analytics. Google Search Console also reports field data for pages with sufficient traffic. Lighthouse provides lab data for development.
What is the most impactful single performance optimization?
Adding proper HTTP cache headers to static assets. It requires zero code changes, takes 10 minutes to configure in nginx, and eliminates redundant downloads for returning users. This alone can reduce page load time by 40-60% for repeat visitors.
How much does a CDN actually improve performance?
For a globally distributed audience, a CDN reduces Time to First Byte (TTFB) by 50-80% for static assets. For a US-only audience with a US-based origin server, the improvement is 20-40%. The benefit increases with geographic distance between users and origin.
Should I use Redis or Memcached for application caching?
Redis for most use cases. It supports data structures beyond key-value (lists, sets, sorted sets), persistence, and pub/sub. Memcached is faster for simple key-value caching but lacks Redis's versatility. Redis uses approximately 30% more memory per key.
How often should I run load tests?
Run load tests before every major release and after any infrastructure change. Integrate a lightweight smoke load test (30 seconds, 10 virtual users) into your CI/CD pipeline. Run full load tests (15 minutes, production-level traffic) weekly or bi-weekly.
What database connection pool size should I use?
Start with `pool_size = (CPU cores * 2) + effective_spindle_count`. For an Arxentra VPS with 4 cores and NVMe storage (effective_spindle_count of 1 for SSD/NVMe), that is 4 x 2 + 1 = 9 connections. Monitor `pg_stat_activity` for connection utilization. Most web applications need 10-25 connections; exceeding 100 usually indicates a connection leak. See Documentation for connection pooling configuration guides.