Chapter 22 β Browser APIs & Web Performance
π Definition
The browser exposes APIs beyond the JavaScript language itself: the DOM, events, storage, networking (fetch, CORS), observers, and background threads (Web Workers). Web performance is how quickly a page loads and responds, measured by Google's Core Web Vitals.
π Critical Rendering Path
HTML β DOM ββ
ββ Render tree β Layout (reflow) β Paint β Composite
CSS β CSSOMβ- CSS is render-blocking; a
<script>withoutdefer/asyncis parser-blocking. defer: download in parallel, run after parsing, in order.async: download in parallel, run as soon as ready, in any order.- Reflow (layout) is expensive; it is triggered by changing geometry (width, top, font-size) or by reading layout (
offsetHeight) right after writing. Repaint is cheaper (colour).transformandopacityonly need compositing β use them for animation.
π Core Web Vitals
| Metric | Measures | Good | Common fixes |
|---|---|---|---|
| LCP β Largest Contentful Paint | Loading | β€ 2.5 s | Optimise hero image, preload, SSR, CDN |
| INP β Interaction to Next Paint | Responsiveness | β€ 200 ms | Break up long tasks, useTransition, Web Workers |
| CLS β Cumulative Layout Shift | Visual stability | β€ 0.1 | Set width/height on images, reserve space for ads |
π» Code Example β Event Delegation
// β One listener per row (1,000 rows β 1,000 listeners)
// β
One listener on the parent, using bubbling
document.querySelector("#todo-list").addEventListener("click", (e) => {
const btn = e.target.closest("button[data-action]");
if (!btn) return;
const id = btn.closest("li").dataset.id;
if (btn.dataset.action === "delete") deleteTodo(id);
if (btn.dataset.action === "toggle") toggleTodo(id);
});Works for elements added later, too. React itself delegates all events to the root container.
π» Code Example β Avoid Layout Thrashing
// β Read-write-read-write forces a reflow on every iteration
items.forEach((el) => { el.style.width = el.offsetWidth + 10 + "px"; });
// β
Batch reads, then writes
const widths = items.map((el) => el.offsetWidth);
items.forEach((el, i) => { el.style.width = widths[i] + 10 + "px"; });
// β
Schedule visual updates for the next frame
requestAnimationFrame(() => { box.style.transform = `translateX(${x}px)`; });π» Code Example β IntersectionObserver (Lazy Load, Infinite Scroll)
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
});
},
{ rootMargin: "200px" }, // start loading 200px before visible
);
document.querySelectorAll("img[data-src]").forEach((img) => observer.observe(img));
// Simple case: <img loading="lazy" src="..." width="400" height="300">π» Code Example β Web Worker for Heavy Computation
// worker.js
self.onmessage = ({ data }) => {
const result = data.rows.filter((r) => r.amount > 1000).sort((a, b) => b.amount - a.amount);
self.postMessage(result);
};
// main.js
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
worker.postMessage({ rows: hugeArray });
worker.onmessage = ({ data }) => render(data); // UI stays responsive meanwhileWorkers have no DOM access; data is copied using the structured clone algorithm (or transferred with Transferables like ArrayBuffer).
π» Code Example β Storage Options
| Capacity | Lifetime | Sent to server | Accessible from JS | |
|---|---|---|---|---|
| Cookie | ~4 KB | Set by Expires/Max-Age |
β every request | β if HttpOnly |
localStorage |
~5β10 MB | Until cleared | β | β (sync, main thread) |
sessionStorage |
~5 MB | Tab lifetime | β | β |
| IndexedDB | Large (quota-based) | Until cleared | β | β (async) |
| Cache API | Large | Until cleared | β | β (service workers) |
Security: never store access tokens in localStorage if you can avoid it β any XSS can read it. Prefer an HttpOnly; Secure; SameSite cookie for the refresh token.
π CORS in One Picture
Browser on https://app.com ββfetchβββΆ https://api.com
1. Is it a "simple" request (GET/POST, simple headers)? β send it, check response headers
2. Otherwise send a PREFLIGHT: OPTIONS /orders
Origin: https://app.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type
3. Server must answer:
Access-Control-Allow-Origin: https://app.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Allow-Credentials: true (if cookies are sent)// Express
import cors from "cors";
app.use(cors({ origin: ["https://app.com"], credentials: true }));CORS is enforced by the browser, not the server β Postman and curl ignore it. With credentials: true, Allow-Origin cannot be *.
π» Code Example β Measuring Performance
performance.mark("start-render");
renderDashboard();
performance.mark("end-render");
performance.measure("render", "start-render", "end-render");
console.log(performance.getEntriesByName("render")[0].duration);
// Long tasks (>50 ms) block input
new PerformanceObserver((list) => {
list.getEntries().forEach((e) => console.warn("Long task", e.duration));
}).observe({ type: "longtask", buffered: true });π Real-World Use
- Product grid with thousands of images β
loading="lazy", explicit dimensions (CLS), modern formats (WebP/AVIF). - Excel import parsed in the browser β Web Worker so the UI does not freeze.
- Infinite scroll feed β IntersectionObserver on a sentinel element.
- "Works in Postman but not in the browser" β CORS configuration.
π― Likely Interview Questions
- Explain the critical rendering path.
- Difference between
asyncanddefer? - What is reflow vs repaint? How do you avoid layout thrashing?
- What is event delegation and why is it efficient?
- What are the Core Web Vitals and how do you improve each?
- Cookies vs localStorage vs sessionStorage β where would you store a JWT?
- What is CORS? What is a preflight request? Why does the request work in Postman?
- When would you use a Web Worker?
β Advanced Async Patterns | Index | Next: Functional Components β