Forced synchronous layout: reading geometry after writing to the DOM
What is happening
Normally layout happens after your JavaScript finishes, once per frame. But if you change the DOM and then immediately read a layout property — offsetWidth, scrollHeight, getBoundingClientRect(), getComputedStyle() — the browser must stop and compute layout right there, synchronously, inside your task, because the answer depends on your pending change.
One forced layout is cheap on a small page. On a large DOM, or repeated in a loop (which becomes layout thrashing), it adds hundreds of milliseconds to the handler.
How to recognise it
- Long processing time in the INP breakdown, with Layout blocks nested inside the script task in DevTools Performance (“Forced reflow” warning).
- Long Animation Frames API reports a large forcedStyleAndLayoutDuration for your script.
- The handler both mutates the DOM and measures elements.
How to fix it
- Read all geometry before the first DOM write in the task.
- Cache measurements that don't change (element sizes, container width) instead of re-reading them on every event.
- If you must measure after a change, defer the read to requestAnimationFrame of the next frame, or use ResizeObserver / IntersectionObserver instead of polling geometry.
Problem
grid.style.width = next + "px" // write: layout is now dirty
const rect = cell.getBoundingClientRect() // read → forced synchronous layout
Fix
const rect = cell.getBoundingClientRect() // read first, layout is clean
grid.style.width = next + "px" // write after; browser lays out
// once, before the next paint
Scan your site for free →