Slow sites are usually death by a thousand cuts. These seven cuts, with bandages:
The list
1. Layout thrashing โ alternating reads and writes forces re-layout each time:
// โ read-write-read-write
items.forEach(el => { el.style.height = el.offsetHeight * 2 + "px"; });
// โ
batch: read all, then write all
const heights = [...items].map(el => el.offsetHeight);
items.forEach((el, i) => el.style.height = heights[i] * 2 + "px");2. DOM updates in a loop โ build a string/fragment, insert once:
list.innerHTML = rows.map(r => `<li>${r}</li>`).join("");3. Unthrottled scroll/resize handlers โ debounce, throttle, or better: IntersectionObserver.
4. A listener per item โ 500 rows = 500 listeners. Use delegation: one.
5. Importing whole libraries โ 70KB of lodash for one debounce function. Write the 8 lines or import the single module.
6. Blocking scripts in head โ always defer; dynamic-import() rarely-used features.
7. Animating layout properties โ width/top/margin trigger layout every frame; transform/opacity don't.
Measure first
DevTools Performance tab: record, find the long tasks, fix THOSE. Optimizing unmeasured code is superstition โ Lighthouse + the profiler turn it into engineering.