Tech Literacy for PMs
7 / 9
Lesson 7 of 9

Session 07: Performance & Scaling — Bottlenecks, CDN, and Web Vitals

17 min readViet-Anh NguyenViet-Anh Nguyen
What you'll get out of this session

What you'll get out of this session

"The app is slow" is the most useless bug report you can hand a developer. After this session, you'll know how to ask the right question.

You'll learn:

  • What Core Web Vitals are and why Google uses them to rank web pages
  • Where the bottleneck lives — frontend, backend, database, or network?
  • How caching helps and why "stale data" is a real tradeoff
  • How CDNs and lazy loading affect performance
  • How to run Lighthouse and read the report to create actionable tickets
  • How to write performance requirements you can measure

Duration: 90–120 minutes, including 30 minutes running Lighthouse on a real product

1/8

Part 1: Core Web Vitals — Why Google cares, and why you should too

The system is up, deployed, and secured. But does it run fast? And when traffic is 10x, is it still fast? "The app is slow" is a useless report — after this session, you'll know how to ask the right question.

The three metrics and what they mean in practice

You land on a shopping site. Three common frustrations:

  1. You stare at a blank white screen for 4 seconds before any content shows up — that's poor LCP.
  2. You're about to tap "Buy now" but an ad banner suddenly pops in, pushing the button down, and you tap the ad by mistake — that's poor CLS.
  3. You tap the "Filter by price" filter but the dropdown takes 2 seconds to respond — that's poor INP.

LCP (Largest Contentful Paint) measures the time from when a user navigates to a page until the largest element in the viewport is rendered — usually the hero image or the main heading. LCP stands in for the feeling that "the page has finished loading."

Common causes of poor LCP:

  • Slow server response time (high TTFB — Time to First Byte)
  • Large images not optimized for size and format
  • Render-blocking CSS and JavaScript (they have to download and parse before anything renders)
  • No CDN for static assets

CLS (Cumulative Layout Shift) measures the total "disruption" of the layout during load. A CLS score of 0.25 means elements move significantly, causing mis-taps.

Common causes of CLS:

  • Images and videos without declared dimensions — the browser doesn't know how much space to reserve
  • Dynamic content injected into the page (ads, banners) after render is done
  • Web fonts swapping in after they load, shifting text around

This is a bug PMs often create when they ask: "Add a promotion banner above the fold after the page loads" — a banner injected afterward causes everything below it to shift.

INP (Interaction to Next Paint) replaced FID in March 2024. It measures response time after a user interaction (click, tap, keyboard). Poor INP is usually caused by heavy JavaScript hogging the main thread, so the browser can't handle the user's click in time.

Why mobile is often several times worse than desktop

Lighthouse has two profiles: desktop and mobile. Mobile scores are usually 20–40 points lower than desktop.

Why: Lighthouse simulates a mid-range device with a CPU 4x slower and a 3G network. This reflects the reality for most users in Vietnam — not an iPhone 15 on WiFi.

PMs should always look at the mobile score, not the desktop one. A high desktop score but a low mobile score is a real problem.

ScoreColorMeaningAction
90–100GreenGoodMaintain
50–89Yellow/orangeNeeds improvementCreate tickets for Opportunities
0–49RedPoorPrioritize a fix now

After running Lighthouse, look at the Opportunities section: each line has a concrete estimated savings (for example: "Serve images in next-gen formats — savings 540 KiB"). This is the basis for creating a ticket with a clear estimated impact.


Part 2: Bottleneck analysis — Find the right problem

The waterfall chart in the Network tab

Open F12 → Network tab → reload the page. You'll see a waterfall chart: each row is a request, its width is the time — like the Gantt chart of a small project, where each row is a "task" the browser is doing.

Look for:

  • The longest row: Which request takes the most time?
  • High TTFB (Time To First Byte): The server takes a long time to start returning data — usually a backend or database problem
  • Lots of JS/CSS files: Each file is a round-trip, blocking render
  • Large images: An image file > 500KB is a warning sign

N+1 query — When the list page slows down with item count

The N+1 query is a logic bug, not a code bug — and it usually comes from how the backend was designed in a hurry. Imagine you need to get the details of 20 students in a class. The slow way: ask for each student's name one at a time (20 questions). The fast way: ask once, "give me the list of 20 students" (1 question). That's exactly the difference.

When a PM notices: "The order list page loads fast at 5 orders, slow at 50, and times out at 200" — this is the classic sign of N+1. The fix: use a JOIN or batch loading instead of a separate query per item.

What a PM can do: In the acceptance criteria for any list page, add: "The API must perform equivalently for 10, 100, and 1000 items."

Database indexes and query optimization

When an API is slow and TTFB is high, the cause is often a database query with no index. Imagine a 1000-page book with no table of contents. To find a word, you have to read every page. That's a database with no index. With an index (the table of contents), the database knows exactly which page to go to.

Worth noting: a missing index doesn't cause problems while data is small. It bites when the data grows — and it usually shows up late, after launch.

The signal: Feature X works well for the first 3 months, starts slowing down in month 4, and the developers changed nothing. The data grew — the missing index was there from day one, it just hadn't caused a problem yet.

Loading diagram…

Part 3: Optimization techniques — What PMs need to know

Image optimization — Usually the biggest quick win

Unoptimized images are the most common cause of a low Lighthouse score. On most e-commerce sites, images account for 60–80% of total page weight. Optimizing images is usually the cheapest, fastest way to improve LCP.

What PMs should require in the spec for any feature with images:

  • Images must be served through a CDN
  • Product images: 100KB max, WebP format (25–35% smaller than JPEG at equivalent quality)
  • Images must have width and height attributes so the browser reserves space ahead of time (prevents CLS)
  • Below-the-fold images must use lazy loading
  • Thumbnails must be their actual size, not a full-size image shrunk down with CSS

Code splitting and bundle size

A large JavaScript bundle slows down the initial load. Imagine each KB of JS as a page in a book — the browser has to read all of them before the user can interact. Every KB has to be downloaded, parsed, and compiled.

Code splitting is the technique of breaking the JS into multiple parts — instead of loading the entire app at once, you only load what the current page needs. For example: a user on the homepage doesn't need to download the JS for the admin page.

What PMs need to know: When you add a third-party library (analytics, chatbot, social widgets), each one adds JavaScript to the bundle. "Add Intercom to the app" isn't just a configure task — it's adding tens of KB of JS that runs on every page load. Ask developers about the impact before deciding to add a new tool.

Caching strategy — Recap and application

Browser cache: CSS, JS, and image files that don't change often should be cached for a long time (1 year). When you ship a change, rename the file to force the browser to download it again.

CDN cache: Images and static assets should be cached at the CDN. The time-to-live (TTL) — how long it lives in the cache — must match how often the content changes. For example: product images cached for 7 days, campaign banner images cached for 1 hour.

API response cache: Results from APIs that don't change often (product categories, configuration, static content) should be cached at the application layer. The trade-off: the data can be stale — a little out of date compared to the real data. The PM has to spec the "acceptable staleness" for each type of data. For example: product prices shouldn't be cached for long, but category names can be.


Part 4: Lighthouse and performance monitoring

Reading a Lighthouse report

Lighthouse breaks performance into 5 scores and many diagnostic items:

Metrics (scores): LCP, FID/INP, CLS, FCP (First Contentful Paint), SI (Speed Index). Each metric is green/yellow/red.

Opportunities: What you can do to improve — each with a concrete estimated savings. For example: "Serve images in next-gen formats — potential savings of 540 KiB" is a concrete opportunity you can turn into a ticket right away.

Diagnostics: Technical information about problems that don't directly affect the score but do affect the experience. This is usually what developers need to read more carefully.

The steps a PM should take after running Lighthouse:

  1. Screenshot the current score — this is the baseline to compare against later
  2. List every Opportunity with estimated savings > 500ms
  3. Create tickets for the top 3 opportunities with acceptance criteria: "LCP must be < 2.5s after implementation"
  4. Re-run Lighthouse after the fix to measure the actual impact

Real User Monitoring vs lab testing

Lighthouse is lab testing — it measures under controlled conditions, simulating an average device. It doesn't accurately reflect real users' experience, because network conditions, device specs, and browser cache state all differ in reality.

Real User Monitoring (RUM) measures real performance from real users' browsers. Think of Lighthouse as a lab test with a mannequin, and RUM as data from actual customers using your product.

Google CrUX (Chrome User Experience Report) is the most freely available RUM data source — aggregate data from millions of real Chrome users. Check the CrUX data for your domain at https://pagespeed.web.dev — it shows both Lab data (Lighthouse) and Field data (CrUX) side by side.

Go to pagespeed.web.dev → enter a URL → look at the two sections: Lab data (Lighthouse synthetic test) and Field data (CrUX from real Chrome users). Field data is what reflects the actual user experience — Lab data is just a reference point for debugging.


Further reading


Homework

Measure your product page's speed — no technical knowledge required.

Google has a free tool that measures a website's speed and explains it in plain language.

  1. Go to pagespeed.web.dev → enter the URL of the most important page in your product (homepage, product page, or checkout) → click Analyze.
  2. Wait 30 seconds. Look at the Mobile score (more important than Desktop, since most users are on phones).
  3. Scroll down to the "Opportunities" section — this is the list of specific problems Google found, along with an estimate of how many seconds each could save.
  4. Pick the one problem that's easiest to understand (usually "Properly size images" or "Eliminate render-blocking resources").
  5. Share it with the group: Screenshot the mobile score, and paste the contents of one "Opportunity" you want to ask developers about — how to fix it and how long it'll take.

What matters

  1. 1Core Web Vitals (LCP, CLS, INP) are a Google ranking signal and a good proxy for real user experience. The mobile score matters more than the desktop score.
  2. 2The bottleneck has to be identified before you fix it. Use the F12 Network tab to distinguish: frontend (asset size, render-blocking), backend (TTFB, API latency), database (query time).
  3. 3The N+1 query is a common reason list pages slow down with scale. The sign: good performance at first, getting worse as the data grows.
  4. 4Image optimization is usually the biggest quick win. Require: CDN, WebP format, size < 100KB, width/height attributes, lazy loading for below-the-fold images.
  5. 5A performance requirement has to be measurable: 'LCP < 2.5s on 3G mobile', 'API P95 < 500ms', not 'the app must be fast'.