Skip to main content

PERFORMANCE IS ONE PIPELINE

Users experience performance as one thing.

Your system is a pipeline:

Senior fullstack performance means you manage budgets across that pipeline.

Pipeline with budget allocations (example targets):

┌─────────────────────────────────────────────────────────────────────────────────┐
│ END-TO-END PERFORMANCE PIPELINE │
│ Target: TTFB p95 ≤ 400ms | LCP p75 ≤ 2.5s | INP p75 ≤ 200ms │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ [User] │
│ │ │
│ ▼ DNS (20ms) ──► TLS (30ms) ──► Request (10ms) │
│ │ │
│ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CDN │────►│ Server │────►│ DB │ │
│ │ (cache?) │ │ (compute) │ │ (queries) │ │
│ │ ~0-50ms │ │ ~50-150ms │ │ ~20-80ms │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ │ └────────────────────┴────────────────────┘ │
│ │ TTFB budget │
│ ▼ │
│ [Response] ──► Parse ──► Render ──► Hydrate ──► [Interact] │
│ ~20ms ~100ms ~80ms INP budget │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

When one stage exceeds its budget, downstream stages get less. Budget allocation is zero-sum.


LATENCY BUDGETING (THE MODEL)

Pick targets:

  • TTFB p95

  • LCP p75

  • INP p75

Then allocate budgets:

  • server compute

  • DB/query time

  • cache hit rate targets

  • client render/hydration

Concrete budget table example — Product page:

StageBudgetNotes
DNS20msUse preconnect, keep connections warm
TLS30msSession resumption, HTTP/2
Network RTT40msAssume ~20ms each way for typical user
CDN / edge0–50ms0 if cache hit, 50ms if miss + origin fetch
Server compute100msAuth, routing, template, serialization
DB / queries50msSum of all queries for the request
TTFB total200msp95 target for product page
Parse + first paint80msHTML + critical CSS
LCP element render120msHero image + above-fold content
LCP total400msFrom navigation start
Hydration80msInteractive components ready
INP150msClick/tap to response

Senior rule:

If you don't budget, you will overspend unpredictably.

Break down your own critical path. If TTFB is 600ms and DB is 400ms, you know where to work.


WHAT TO MEASURE FIRST (ANTI-PLACEBO)

Start with:

  • end-to-end traces for slow requests

  • DB query timings + frequency

  • cache hit rates

  • payload sizes (JSON, images)

Avoid:

  • "micro-optimizations" without a measured bottleneck

Tools by layer:

LayerToolUse case
End-to-end (synthetic)Lighthouse, WebPageTestCI, pre-release checks, lab conditions
End-to-end (real users)RUM (e.g., Vercel Analytics, SpeedCurve)p75/p95 in production
Server tracesOpenTelemetry, Datadog APM, JaegerRequest spans, DB calls, cache hits
DBpg_stat_statements, slow query logs, EXPLAIN ANALYZEQuery time, frequency, plans
ClientChrome DevTools Performance, Web Vitals APILCP, INP, long tasks, layout thrash

RUM vs synthetic:

  • Synthetic: Controlled environment, reproducible, good for CI. Misses real network, devices, and user behavior.
  • RUM: Real conditions, real percentiles. Needs volume; noisy on low-traffic pages.

Senior rule:

Measure before you optimize. Fix the biggest bottleneck first.


CACHING HIERARCHY

Layers:

  1. browser (assets)

  2. CDN (cacheable GETs)

  3. server/app cache (hot computed results)

  4. DB/query cache (where appropriate)

Keys:

  • include auth scope

  • include tenantId

  • include version

Invalidation:

  • prefer event-driven invalidation for correctness-critical data

  • otherwise TTL + stale-while-revalidate

Stampede control:

  • request coalescing

  • jittered TTL

  • soft/hard TTL

Decision table — Data type → Cache layer → TTL → Invalidation:

Data typeCache layerTTLInvalidation strategy
Static assets (JS, CSS, images)CDN + browser1y (immutable)Cache-busting filename / version in URL
Product catalog (read-heavy)CDN + app5–15 minEvent: product updated → purge key
User session / authApp (Redis/Memcached)Session lifetimeEvent: logout → delete
Search resultsApp1–5 minTTL + stale-while-revalidate
Dashboard aggregatesApp30s–2 minTTL; event-driven if real-time matters
User-specific lists (e.g., cart)App, keyed by userIdPer-request or short TTLNot CDN-cacheable; auth in key

Senior rule:

Cache keys must encode everything that affects the response. Omit tenantId once and you have a data leak.


PAYLOAD BUDGETS (FRONTEND IS BACKEND TOO)

Large payloads kill:

  • mobile networks

  • render time

  • memory

Senior tactics:

  • pagination and windowing

  • field selection (don't ship unused fields)

  • compress responses

  • image resizing and modern formats

Concrete size targets:

Payload typeTargetRationale
Initial JS (gzipped)< 200KBParse + compile cost; mobile CPUs
Critical CSS< 50KBBlocking render
API response (list)< 50KBFast parse, low memory
API response (single entity)< 20KBAvoid over-fetching
Hero image (above fold)< 100KBLCP impact
Total above-fold< 500KB3G-like conditions

GraphQL and field selection:

  • GraphQL lets clients request only needed fields. Use it.
  • Default to a minimal "list view" fragment; expand only for detail views.
  • Avoid * or "fetch everything" patterns. Enforce field allowlists server-side.
  • Paginate connections: first: 20, not unbounded.

Senior rule:

Every byte you send is a byte the user pays for. Ship only what the view needs.


RENDERING STRATEGIES (SSR/CSR/STREAMING)

Rules of thumb:

  • SSR for fast first content

  • CSR for highly interactive subtrees

  • streaming when you can progressively reveal content

But always measure real user metrics.

Tradeoff table:

StrategyFirst contentTTI / interactivitySEOComplexityUse when
SSRFastSlower (hydration)FullMediumMarketing, product pages, content
CSRSlower (fetch + render)After loadNeeds prerender/crawlLowerDashboards, apps, auth-gated
Streaming (e.g., Suspense)Fast + progressiveProgressiveGoodHigherLong pages, feeds, modular content
ISR (Incremental Static Regeneration)FastFast (static)FullMediumContent that can be stale 60s–1h

ISR: Pre-render at build or on first request; revalidate in background. Best for product catalogs, blogs, config-driven pages.

Hydration cost:

  • Hydration = running framework JS to attach event listeners and make SSR HTML interactive.
  • Cost scales with DOM size and component count. Large trees = long main-thread blocks = bad INP.
  • Mitigations: partial hydration (only hydrate above-fold or critical widgets), lazy hydration, islands architecture.

Senior rule:

Choose rendering strategy based on what you need first: content speed, interactivity, or both. Don't default to "full SSR" or "full CSR" without a reason.


PROFILING PLAYBOOK

  1. Reproduce slow path (prod traces > local guessing)

  2. Identify bottleneck category: IO, DB, CPU, serialization, client render

  3. Fix the bottleneck (one change)

  4. Measure again

Tools per layer:

LayerToolWhat to look for
ClientChrome DevTools PerformanceLong tasks (>50ms), layout thrash, forced reflows
ClientLighthouse, Web VitalsLCP, INP, TBT, CLS
ClientReact DevTools Profiler, flamegraphsComponent render cost, unnecessary re-renders
ServerOpenTelemetry / APM tracesSpan duration, DB vs compute vs external calls
DBpg_stat_statements, slow query logTotal time, calls, mean time per query
DBEXPLAIN ANALYZEMissing indexes, seq scans, high cost
NetworkDevTools Network, WebPageTestWaterfall, TTFB, payload sizes

Profiling decision tree:

                    [Request is slow]

┌───────────────┼───────────────┐
▼ ▼ ▼
[TTFB high?] [LCP slow?] [INP bad?]
│ │ │
▼ ▼ ▼
Check server Check: Check:
traces - LCP element - Long tasks
- DB spans? - Image size? - Event handlers
- Cache miss? - Render block? - Main thread
- Serialization? - Fonts? - Hydration
│ │ │
▼ ▼ ▼
[DB slow] → [Resource] → [JS] →
EXPLAIN, Optimize Code-split,
indexes delivery defer, lazy

Senior rule:

One change at a time. Measure. Then the next change.


PERFORMANCE REGRESSION PREVENTION

Budgets are useless if regressions ship. Automate.

CI performance budgets:

  • Run Lighthouse (or similar) on every PR for critical routes.
  • Fail the build if budgets are exceeded.
  • Use lighthouse-ci or @lhci/cli to enforce.

Lighthouse CI example:

# .github/workflows/perf.yml
- name: Run Lighthouse CI
run: |
npx @lhci/cli@0.12.x autorun

Configure lighthouserc.js with budgets:

// lighthouserc.js
module.exports = {
ci: {
assert: {
assertions: {
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'interactive': ['error', { maxNumericValue: 3500 }],
},
},
},
};

Bundle size tracking:

  • Use bundlesize or size-limit in CI.
  • Fail if JS/CSS bundles grow beyond thresholds.
  • Track trends (e.g., bundlesize with GitHub Actions).

p95 alerting:

  • In production, alert when p95 TTFB, LCP, or INP exceeds budget.
  • Use RUM (e.g., Vercel Analytics, SpeedCurve, custom) to trigger PagerDuty/Slack.

Senior rule:

If it's not in CI or alerting, it will regress. Guaranteed.


REAL-WORLD BUDGET EXAMPLE

Dashboard page — end-to-end budget:

MetricBudgetAllocation
TTFB p95400msDNS 20 + TLS 30 + RTT 40 + CDN 50 + Server 150 + DB 110
LCP p752.5sTTFB 400 + HTML 100 + CSS 80 + JS 200 + Render 200 + LCP image 1520
INP p75200msClick → handler < 150ms + paint < 50ms
Initial JS180KB gzipFramework 80 + app 70 + vendor 30
API (dashboard data)45KBPaginated, field selection

Assumptions:

  • Dashboard is auth-gated; no CDN for API. Server + DB must stay within 260ms.
  • LCP element = chart or primary data table. Image/asset optimized.
  • INP: table sorting, filters, modals. Handlers kept small; no 100ms+ sync work.

When budget is exceeded:

  1. TTFB > 400ms → Trace server; check DB query plan, cache hit rate, N+1.
  2. LCP > 2.5s → Reduce JS, defer non-critical, optimize LCP resource.
  3. INP > 200ms → Profile handlers; break up long tasks; consider Web Workers.

EXERCISES

  1. Define budgets for one critical page (TTFB/LCP/INP).

  2. List your caching layers and which data belongs in each.

  3. Pick one endpoint and cut payload size by 30% (design how).

  4. Add a Lighthouse CI budget to one route and make the build fail when exceeded.

  5. Draw a profiling decision tree for your slowest page.


🏁 END — END-TO-END PERFORMANCE BUDGETS