Performance Tips

Highest-leverage practices that improve perceived and real speed. Measure first — optimise after.
Critical

Measure before you optimise

Why it matters

Guesswork optimisation wastes time and usually speeds up the wrong place. Without data you can't measure impact.

How to apply

Lab measurement with Lighthouse / WebPageTest; RUM for real users — Web Vitals JS, Sentry Performance, New Relic Browser, Application Insights. Track p75 and p95.

Example
p50 may look fast, but if p95 is slow, 5% of users have a poor experience.
High

Hit Core Web Vitals targets: LCP < 2.5s, INP < 200ms, CLS < 0.1

Why it matters

Google uses CWV as ranking signals and they directly reflect user perception. Missing thresholds hurts SEO and conversion.

How to apply

For LCP, preload the hero image at correct size/format. For INP, keep main thread free of long tasks (web workers, code split). For CLS, set explicit dimensions on images/iframes, plan font swap.

Example
<img width="1200" height="630" loading="eager" fetchpriority="high"> can drop hero LCP by 500ms.
High

Serve images in modern formats at correct size

Why it matters

Images are typically 50%+ of payload. AVIF/WebP shrink them 30–80% vs PNG/JPG. Downloading at wrong size and scaling via CSS wastes bandwidth.

How to apply

<picture> with AVIF→WebP→JPEG fallback. srcset/sizes for responsive. loading="lazy" on below-the-fold images. CDN-based on-the-fly resize (Cloudflare Images, ImageKit, Cloudinary).

Example
Next.js: <Image>; .NET: ImageSharp.Web with URL params (?width=800&format=avif) for on-demand resize.
High

Keep JavaScript bundle size in check

Why it matters

JS is downloaded, parsed, and executed — three costs. On a 3G mobile device, 200KB of JS means 4–6 seconds of CPU time.

How to apply

Code splitting (route-based, dynamic import). Tree-shaking. Avoid unnecessary polyfills (modern target). Use bundle analyzer to find big modules. Count third-party scripts in your perf budget.

Example
Target: critical JS < 100KB gzip. moment.js → date-fns/Day.js (~70KB savings). lodash → modular imports.
Critical

Set cache headers and CDN correctly

Why it matters

Serving static assets from the nearest edge and from cache eliminates RTTs. Wrong headers → re-download every time.

How to apply

For hashed files (app.abc123.js): Cache-Control: public, max-age=31536000, immutable. For HTML: no-cache or short max-age + ETag. Enable CDN for all static assets.

Example
Cloudflare/Bunny CDN: cache-everything rule + Cache-Control respected. ASP.NET Core: app.UseStaticFiles + ResponseCachingMiddleware.
Critical

Create the right database indexes

Why it matters

Without indexes, queries do full table scans; latency grows linearly with data and the product 'hits a wall' at scale.

How to apply

Use EXPLAIN/EXPLAIN ANALYZE to read query plans. Index WHERE/JOIN/ORDER BY columns. Order matters in composite indexes; put high-cardinality first. Too many indexes hurt write-heavy tables — balance.

Example
PostgreSQL: CREATE INDEX CONCURRENTLY ON orders(user_id, created_at DESC); — for a page listing a user's orders by date.
High

Detect and eliminate N+1 query problems

Why it matters

ORMs sometimes issue N+1 queries instead of 1. 101 queries for 100 users on a page — devastating for DB and latency.

How to apply

EF Core: .Include / .ThenInclude / projection. Prisma: include / select. Dapper: combine queries. Enable query logging in dev and measure critical pages.

Example
EF Core: db.Orders.Include(o => o.Items).ThenInclude(i => i.Product) — fetch in a single SQL.
High

Enable compression (Brotli/gzip) and HTTP/2 or HTTP/3

Why it matters

Brotli shrinks HTML/JS/CSS by 15–25%. HTTP/2 multiplexing fixes head-of-line blocking; HTTP/3 (QUIC) tolerates packet loss on mobile.

How to apply

Enable Brotli at server/CDN level. Don't re-compress already-compressed images. ASP.NET Core: app.UseResponseCompression with Brotli/Gzip providers.

Example
Cloudflare offers automatic Brotli. Behind Kestrel/IIS, Response Compression Middleware is typical.
Medium

Optimise font loading

Why it matters

Web fonts can block render and cause CLS. Wrong loading produces FOIT (invisible text) or late swap.

How to apply

Use WOFF2. Subset to the weights/character sets you use. font-display: swap. Preload critical font with <link rel="preload">. Variable fonts pack multiple weights in one file.

Example
Inter Variable Latin subset (~30KB) covers all weights; self-hosting beats Google Fonts on speed + is GDPR-friendly.
High

Inline critical CSS, lazy-load the rest

Why it matters

Render-blocking CSS delays LCP. Inlining the minimum CSS for above-the-fold makes first paint instant.

How to apply

Extract critical CSS with Critical/critters/penthouse. Load the rest async via media trick: <link rel="preload" as="style" onload="this.rel='stylesheet'">.

Example
Tailwind: JIT mode generates only used classes; per-page CSS splitting helps further.
Medium

Manage third-party scripts with a budget

Why it matters

Analytics, chat widgets, A/B testing — each adds 50–300KB and many long tasks. Unmanaged, they can halve homepage speed.

How to apply

Only load what's truly needed. Use async/defer. When possible, offload to a worker via Partytown. Measure with Lighthouse 'third-party-summary'.

Example
Session replay tools like Hotjar/FullStory degrade INP heavily; sampling at 5–10% usually gives enough signal.
High

Mind connection pooling and concurrency limits

Why it matters

Opening a fresh DB connection per request is expensive; if the pool fills, requests queue and slowdowns cascade.

How to apply

Tune connection pool size by CPU/RAM. Use a singleton HttpClient for outbound APIs (IHttpClientFactory). Keep async/await chain unbroken.

Example
.NET: services.AddHttpClient<ApiClient>(); — handler pool automatic. EF Core: Pooling=true (default), Max Pool Size in connection string.
Medium

Stream large responses; don't buffer them in memory

Why it matters

Buffering large JSON/files in memory adds GC pressure and uses RAM; under high concurrency this risks OOM.

How to apply

ASP.NET Core: return IAsyncEnumerable, use FileStreamResult. Node: response.pipe(stream). Push big import/export jobs to a queue.

Example
10K-row CSV export → background job + signed URL for the result; don't do it inline over HTTP.
High

Cache hot paths and plan invalidation

Why it matters

Running the same query 1000x/sec is wasteful. Cache yields 10–100x speedups, but 'cache invalidation' is one of the two hardest problems in software.

How to apply

Read-heavy, rarely changing data: cache with Redis/Memcached. Design cache keys carefully (include a version). Combine TTL + event-based invalidation. Use stale-while-revalidate.

Example
.NET 9+: HybridCache (memory + distributed) under one API. CacheKey: "user:{id}:profile:v2".
Medium

Watch for memory leaks in long-lived apps

Why it matters

Server apps run for weeks. A slow leak (event listener, static collection, IDisposable) eventually forces restart or OOMs.

How to apply

Compare heap snapshots (dotnet-counters, VS Diagnostic Tools, Chrome DevTools). Wrap IDisposables with using. If a code path appends to static collections, use a bounded ConcurrentDictionary.

Example
ASP.NET: ConfigureAwait isn't needed (no sync context), but pass cancellation tokens through every async path.