Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services

Blog Posts

Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Back to all blogs

How to Optimize Web Application Performance

Jun 8, 2026•6 min read

Measure before you optimize. Every performance win I've shipped started with real numbers, not a hunch — because the bottleneck is almost never where you assume it is. When I worked on JoVE, a React video platform under heavy traffic, the temptation was to start rewriting components. Instead we profiled first, found the real culprits, and fixed those. This is the priority-ordered playbook I use, from measurement to the specific fixes that move the needle.

Step 1: Measure first

You need a baseline and a way to find your bottleneck. Three tools cover it:

  • Core Web Vitals — the metrics Google and users actually care about.
  • Lighthouse / Chrome DevTools — lab data, run on demand, great for the Performance panel's flame charts.
  • Real User Monitoring (RUM) — field data from actual users on real devices and networks. This is what matters most; lab conditions lie.

The vitals worth watching:

MetricMeasuresGood target
LCP (Largest Contentful Paint)Loading — when the main content appears≤ 2.5s
INP (Interaction to Next Paint)Responsiveness — input lag≤ 200ms
CLS (Cumulative Layout Shift)Visual stability — unexpected jumps≤ 0.1
TTFB (Time to First Byte)Server + network response time≤ 800ms

Profile the real app, identify which metric is worst, and start there. Everything below is ordered roughly by leverage — but your profile decides the order.

Step 2: Ship less JavaScript

For most modern apps, JavaScript is the biggest cost. It has to be downloaded, parsed, and executed — and parse/execute time on a mid-range phone is brutal.

  • Code-split by route so users only download the JS for the page they're on.
  • Lazy-load heavy components (charts, editors, modals) that aren't needed on first paint.
  • Tree-shake — ensure your bundler drops unused exports, and avoid importing an entire library for one function.
js
1import { lazy, Suspense } from "react"; 2 3// Split a heavy, below-the-fold component out of the main bundle 4const Analytics = lazy(() => import("./Analytics")); 5 6function Dashboard() { 7 return ( 8 <Suspense fallback={<Spinner />}> 9 <Analytics /> 10 </Suspense> 11 ); 12}

Check your bundle with a analyzer before and after. A single accidental import of a date or charting library can add hundreds of kilobytes.

Step 3: Optimize images

Images are usually the largest bytes on the page and a common LCP culprit.

  • Serve WebP or AVIF instead of JPEG/PNG — often 30–50% smaller at the same quality.
  • Use responsive images (srcset/sizes) so phones don't download desktop-sized files.
  • Lazy-load off-screen images with loading="lazy".
  • Always set width and height (or use a framework <Image> component) to prevent layout shift and protect CLS.
html
1<img 2 src="hero.avif" 3 srcset="hero-480.avif 480w, hero-960.avif 960w, hero-1440.avif 1440w" 4 sizes="(max-width: 600px) 480px, 960px" 5 width="960" height="540" 6 loading="lazy" decoding="async" 7 alt="Product screenshot" 8/>

Step 4: Eliminate render-blocking resources

Anything the browser must fetch before it can paint delays your LCP.

  • Inline critical CSS and defer the rest.
  • Defer non-essential JavaScript with defer or async so it doesn't block parsing.
  • Preconnect to origins you'll need early (fonts, API, CDN) so the connection is warm when you use it.
html
1<link rel="preconnect" href="https://cdn.myapp.com" crossorigin /> 2<link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin /> 3<script src="/analytics.js" defer></script>

Step 5: Cache aggressively at every layer

Caching is the highest-leverage backend work because the fastest request is the one you never make.

  • Browser cache — long Cache-Control max-age on hashed static assets (they're immutable).
  • CDN cache — serve static assets and cacheable API responses from edge locations near the user.
  • Server cache — cache expensive computed results and hot queries (Redis, in-memory) instead of recomputing per request.
Cache-Control: public, max-age=31536000, immutable   # hashed assets
Cache-Control: public, s-maxage=60, stale-while-revalidate=300  # API at the CDN

Step 6: Tune the backend

If TTFB is your problem, the fix is server-side. The usual suspects, in order of how often they're the cause:

  • Missing indexes. Add indexes on the columns you filter and join on. A missing index turns a millisecond lookup into a full table scan.
  • N+1 queries. One query to fetch a list, then one query per item — the classic ORM trap. Fix it by eager-loading / joining. This was a recurring win on high-traffic pages.
  • Connection pooling. Reuse database connections instead of opening one per request; opening connections is expensive.
sql
1-- Turn a full scan into an index lookup 2CREATE INDEX idx_orders_user_id ON orders (user_id); 3 4-- Then EXPLAIN ANALYZE to confirm the planner uses it 5EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 42;

Step 7: Optimize the network layer

  • Put a CDN in front of everything static.
  • Use HTTP/2 or HTTP/3 for multiplexing (most managed hosts and CDNs give you this for free).
  • Enable Brotli or Gzip compression on text responses (HTML, CSS, JS, JSON).

Step 8: Reduce re-renders on the frontend

This is where React-heavy apps like JoVE spend their INP budget. Unnecessary re-renders make interactions feel laggy.

  • Memoize expensive components and computations (React.memo, useMemo, useCallback) — but only where profiling shows a hot path, not everywhere.
  • Virtualize long lists so you render only the visible rows, not all 10,000.
  • Debounce high-frequency handlers (search inputs, scroll, resize).
js
1// Render only visible rows for a large list 2import { useVirtualizer } from "@tanstack/react-virtual"; 3// Debounce a search input so you don't fire a request per keystroke 4import { useDebouncedValue } from "./hooks"; 5const debouncedQuery = useDebouncedValue(query, 300);

The priority checklist

When I pick up a slow app, this is the order I work in:

  1. Measure — capture Core Web Vitals + RUM, find the worst metric.
  2. JavaScript — code-split, lazy-load, trim the bundle.
  3. Images — modern formats, responsive, lazy, sized.
  4. Render-blocking — critical CSS, defer JS, preconnect.
  5. Caching — browser, CDN, server.
  6. Backend — indexes, kill N+1, pool connections.
  7. Network — CDN, HTTP/2/3, compression.
  8. Re-renders — memoize hot paths, virtualize, debounce.
  9. Re-measure — confirm the numbers actually moved.

The last step is the one people skip. If you didn't re-measure, you don't know you helped — and sometimes an "optimization" makes things worse.

Where this fits

Performance is one pillar of running a production app. If your app's traffic is spiky, the hosting model matters too — see When Should You Use Serverless Architecture? for how scaling behavior affects performance and cost. For the full picture of taking an app to production, see the hub: Deploying, Scaling & Architecting Full-Stack Apps.

Related Posts

Deploying, Scaling & Architecting Full-Stack Apps

A practical map for taking a full-stack app from a working codebase to a production system that ships reliably and scales — deployment, CI/CD, performance, and architecture choices.

How to Deploy a Full-Stack Application

A step-by-step walkthrough for deploying a full-stack app to production — environment prep, choosing hosting, deploying the database, backend, and frontend, connecting them, and going live with a custom domain and HTTPS.

How to Set Up CI/CD for a Web Application

Build a CI/CD pipeline that makes deploys boring and safe — the full flow from git push to production, a working GitHub Actions YAML file, deployment strategies, and the best practices that keep pipelines fast and reliable.