5+ years software engineer
5+ years software engineer
5+ years software engineer
5+ years software engineer
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.
You need a baseline and a way to find your bottleneck. Three tools cover it:
The vitals worth watching:
| Metric | Measures | Good 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.
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.
js1import { 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.
Images are usually the largest bytes on the page and a common LCP culprit.
srcset/sizes) so phones don't download desktop-sized files.loading="lazy".width and height (or use a framework <Image> component) to prevent layout shift and protect CLS.html1<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/>
Anything the browser must fetch before it can paint delays your LCP.
defer or async so it doesn't block parsing.html1<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>
Caching is the highest-leverage backend work because the fastest request is the one you never make.
Cache-Control max-age on hashed static assets (they're immutable).Cache-Control: public, max-age=31536000, immutable # hashed assets
Cache-Control: public, s-maxage=60, stale-while-revalidate=300 # API at the CDN
If TTFB is your problem, the fix is server-side. The usual suspects, in order of how often they're the cause:
sql1-- 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;
This is where React-heavy apps like JoVE spend their INP budget. Unnecessary re-renders make interactions feel laggy.
React.memo, useMemo, useCallback) — but only where profiling shows a hot path, not everywhere.js1// 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);
When I pick up a slow app, this is the order I work in:
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.
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.
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.
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.
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.