-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
141: Web Vitals and Measuring Real Performance
Imagine you've just sat down at a high-end restaurant. You're hungry. First, you're waiting for the main course to hit the table—that's the moment you actually feel like the meal has started. Then, while you're eating, imagine the waiter suddenly slides your water glass six inches to the left right as you're reaching for it. Frustrating, right? Finally, imagine you try to catch the waiter's eye to ask for a napkin, but they're staring blankly into space for five seconds before they actually acknowledge you.
That's exactly how a user experiences your website. In the world of Web Vitals, the "main course" is Largest Contentful Paint (LCP). The "sliding water glass" is Cumulative Layout Shift (CLS). And that "blank stare" from the waiter is what we call Interaction to Next Paint (INP) (which has largely replaced First Input Delay as the gold standard for responsiveness).
The "Where is my food?" problem (LCP)
LCP measures how long it takes for the largest visible element—usually a hero image or a big heading—to render on the screen. If your LCP is slow, the user thinks the site is broken or slow, even if the background scripts have already loaded. I've seen developers spend weeks optimizing a database query that saves 50ms, while the user is staring at a blank screen for 3 seconds because a massive, unoptimized 4MB JPEG is blocking the paint.
To measure this in the wild, we don't just rely on Chrome DevTools "Lighthouse" reports (which are synthetic). We use the PerformanceObserver API to see what actual users are experiencing.
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log(`LCP candidate: ${lastEntry.startTime}ms`);
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
Stop moving the furniture (CLS)
CLS is the "annoyance" metric. It happens when an element (like an ad or a late-loading image) suddenly pops into existence and pushes the content the user was about to click further down the page. There is nothing more infuriating than trying to click "Cancel" on a popup, only for the page to shift, and you end up clicking "Confirm Purchase" instead.
The fix is usually simple: always define width and height attributes on your images and ad containers. If you tell the browser "this image will be 300x200," the browser reserves that space before the image downloads, preventing the shift.
The responsiveness lag (INP)
While LCP is about loading, INP is about feeling. It measures the latency of every interaction a user has with your page. If you have a massive JavaScript bundle that blocks the main thread, the browser can't respond to a click or a keypress. The user clicks a button, and... nothing happens for 200ms. It feels "janky."
I usually tell my juniors that INP is the "main thread" tax. If you're running a heavy forEach loop over 10,000 items on the main thread, you're effectively telling the browser to ignore the user until that loop finishes. To fix this, we break long tasks into smaller chunks using setTimeout or requestIdleCallback.
Capturing real-world metrics in your code
Testing on your MacBook Pro with a fiber connection is a lie. Your users are on three-year-old Android phones on spotty 4G. That's why we use the web-vitals library (maintained by Google) or the native PerformanceObserver to send this data to our own analytics backend.
import {onCLS, onINP, onLCP} from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify(metric);
// Using navigator.sendBeacon is best for performance metrics
// because it doesn't block the page unload.
navigator.sendBeacon('/analytics', body);
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
By tracking these three things, you stop guessing if your site is "fast" and start knowing exactly where the friction is.
📋 Practical Task
Implementing a Layout Shift Guard and LCP Tracker
You have a landing page where a large promotional banner loads dynamically via JavaScript, causing a massive layout shift (CLS) and delaying the Largest Contentful Paint (LCP). Your task is to:
- Fix the CLS: Create a CSS "placeholder" or "skeleton" container for the banner with a fixed aspect ratio so the content below it doesn't jump when the image finally arrives.
- Measure the LCP: Implement a
PerformanceObserverscript that listens forlargest-contentful-paintand logs the final LCP value to the console once the page has fully loaded. - Verify: Open the Network tab in DevTools, throttle your connection to "Fast 3G," and ensure the content no longer jumps and the LCP value is accurately captured in the console.
There are no comments for now.