Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Apply 62 React and Next.js performance optimization rules from Vercel Engineering
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/js-batch-dom-css.md
1---2title: Avoid Layout Thrashing3impact: MEDIUM4impactDescription: prevents forced synchronous layouts and reduces performance bottlenecks5tags: javascript, dom, css, performance, reflow, layout-thrashing6---78## Avoid Layout Thrashing910Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.1112**This is OK (browser batches style changes):**13```typescript14function updateElementStyles(element: HTMLElement) {15// Each line invalidates style, but browser batches the recalculation16element.style.width = '100px'17element.style.height = '200px'18element.style.backgroundColor = 'blue'19element.style.border = '1px solid black'20}21```2223**Incorrect (interleaved reads and writes force reflows):**24```typescript25function layoutThrashing(element: HTMLElement) {26element.style.width = '100px'27const width = element.offsetWidth // Forces reflow28element.style.height = '200px'29const height = element.offsetHeight // Forces another reflow30}31```3233**Correct (batch writes, then read once):**34```typescript35function updateElementStyles(element: HTMLElement) {36// Batch all writes together37element.style.width = '100px'38element.style.height = '200px'39element.style.backgroundColor = 'blue'40element.style.border = '1px solid black'4142// Read after all writes are done (single reflow)43const { width, height } = element.getBoundingClientRect()44}45```4647**Correct (batch reads, then writes):**48```typescript49function avoidThrashing(element: HTMLElement) {50// Read phase - all layout queries first51const rect1 = element.getBoundingClientRect()52const offsetWidth = element.offsetWidth53const offsetHeight = element.offsetHeight5455// Write phase - all style changes after56element.style.width = '100px'57element.style.height = '200px'58}59```6061**Better: use CSS classes**62```css63.highlighted-box {64width: 100px;65height: 200px;66background-color: blue;67border: 1px solid black;68}69```70```typescript71function updateElementStyles(element: HTMLElement) {72element.classList.add('highlighted-box')7374const { width, height } = element.getBoundingClientRect()75}76```7778**React example:**79```tsx80// Incorrect: interleaving style changes with layout queries81function Box({ isHighlighted }: { isHighlighted: boolean }) {82const ref = useRef<HTMLDivElement>(null)8384useEffect(() => {85if (ref.current && isHighlighted) {86ref.current.style.width = '100px'87const width = ref.current.offsetWidth // Forces layout88ref.current.style.height = '200px'89}90}, [isHighlighted])9192return <div ref={ref}>Content</div>93}9495// Correct: toggle class96function Box({ isHighlighted }: { isHighlighted: boolean }) {97return (98<div className={isHighlighted ? 'highlighted-box' : ''}>99Content100</div>101)102}103```104105Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.106107See [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.108