브라우저 리플로우: 발생 원인 및 Core Web Vitals에 미치는 영향

리플로우는 요소의 위치를 재계산하고 메인 스레드를 차단합니다. 발생 원인, 감지 방법 및 예방 방법을 확인하십시오.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-10

브라우저 리플로우란 무엇인가?

리플로우는 브라우저가 페이지 내 요소의 위치와 크기를 다시 계산할 때 발생합니다. DOM을 변경하거나 레이아웃에 영향을 미치는 CSS 속성을 수정할 때마다 브라우저는 모든 요소가 배치될 위치를 다시 파악해야 합니다. 이 계산 작업은 메인 스레드를 차단합니다. 작업이 완료될 때까지 다른 어떤 작업도 수행되지 않습니다.

단순한 페이지에서 단일 리플로우는 수 마이크로초가 걸립니다. 하지만 사용자 상호 작용 중에 수백 번의 리플로우를 트리거하면 메인 스레드가 수백 밀리초 동안 차단됩니다. 이는 Interaction to Next Paint 평가를 실패하게 만들기에 충분합니다.

최종 검토: Arjen Karel (2026년 3월)

렌더링 파이프라인

리플로우에 많은 비용이 드는 이유를 이해하려면 브라우저가 페이지를 렌더링하는 방식을 알아야 합니다. 모든 시각적 변화는 최대 5단계를 거칩니다.

JavaScript → Style → Layout → Paint → Composite

JavaScript(또는 CSS)가 시각적 변화를 트리거합니다. 브라우저는 적용할 스타일을 다시 계산합니다. 그런 다음 레이아웃(리플로우)을 실행하여 기하학적 구조를 계산합니다. 그 다음 픽셀을 페인트합니다. 마지막으로 레이어들을 함께 합성합니다.

모든 변경 사항이 5단계를 모두 거치는 것은 아닙니다. 이것이 핵심입니다. width 또는 height를 변경하면 전체 파이프라인이 트리거됩니다. background-color를 변경하면 레이아웃을 완전히 건너뜁니다(페인트 및 합성만 수행). transform 또는 opacity를 변경하면 레이아웃과 페인트를 모두 건너뛰고 바로 합성으로 넘어갑니다. web.dev 렌더링 성능 가이드에서 이 파이프라인에 대해 자세히 다룹니다.

합성만 발생하는 변경 사항은 비용이 적게 듭니다. 레이아웃 변경은 그렇지 않습니다. 60fps 환경에서 브라우저는 모든 작업을 수행하는 데 프레임당 16.66ms의 시간을 갖습니다. 브라우저 오버헤드를 제외하면 코드 실행에 약 10ms가 주어집니다. 이 시간을 리플로우에 소비하면 버벅거림(jank)이 발생합니다.

리플로우 발생 원인

리플로우를 유발하는 요소는 두 가지 범주로 나뉩니다. 현재 레이아웃을 무효화하는 변경 사항과 브라우저가 레이아웃을 즉시 계산하도록 강제하는 JavaScript 읽기 작업입니다.

레이아웃을 트리거하는 CSS 속성

다음 속성 중 하나라도 변경하면 브라우저에 리플로우가 강제됩니다.

  • 크기: width, height, padding, border, min-height, max-width
  • 위치: top, right, bottom, left, margin
  • 레이아웃 모드: display, float, position, flex, grid
  • 텍스트: font-size, font-family, font-weight, line-height, text-align, white-space
  • 콘텐츠: overflow, word-wrap

color, background-color, visibility, box-shadow와 같은 속성은 리페인트를 트리거하지만 리플로우를 트리거하지는 않습니다. transform, opacity, filter와 같은 속성은 둘 다 트리거하지 않습니다. 이러한 속성들은 애니메이션 및 전환(transitions)에 사용하기 적합합니다.

동기식 레이아웃을 강제하는 JavaScript 속성

여기서부터 비용이 많이 듭니다. 특정 JavaScript 속성은 브라우저가 동기적으로 스크립트를 차단하며 즉시 레이아웃을 계산하도록 강제합니다. Paul Irish가 유지 관리하는 포괄적인 목록을 참고할 수 있습니다(GitHub에서 5,000개 이상의 별을 받음). 가장 일반적인 속성은 다음과 같습니다.

  • offsetWidth, offsetHeight, offsetTop, offsetLeft
  • clientWidth, clientHeight, clientTop, clientLeft
  • scrollWidth, scrollHeight, scrollTop, scrollLeft
  • getBoundingClientRect()
  • getComputedStyle()
  • innerText (그렇습니다. innerText를 읽으면 레이아웃이 강제됩니다.)
  • focus()
  • scrollIntoView()

레이아웃 속성을 변경한 후 이러한 속성 중 하나를 읽으면 동기식 리플로우가 강제됩니다. 브라우저는 레이아웃을 먼저 계산하지 않고는 해당 값을 반환할 수 없습니다. Chrome DevTools는 30ms를 초과하는 강제 리플로우를 성능 병목 현상으로 표시합니다.

레이아웃 스래싱(Layout thrashing): 피해야 할 패턴

레이아웃 스래싱은 코드에서 루프 내에 레이아웃 속성을 읽고 쓰는 작업을 번갈아 수행할 때 발생합니다. 이전의 쓰기 작업이 레이아웃을 무효화했기 때문에 각 읽기 작업은 리플로우를 강제합니다. 캐러셀 스크립트, 아코디언 플러그인 및 요소 위치를 측정하는 분석 코드에서 이 패턴이 지속적으로 관찰됩니다.

// 나쁨: 모든 반복마다 리플로우를 강제함
for (const el of elements) {
  el.style.width = box.offsetWidth + 'px'; // 읽기 + 쓰기 = 강제 리플로우
}

모든 반복에서 offsetWidth를 읽고(레이아웃 강제), 그런 다음 style.width에 씁니다(레이아웃 무효화). 100개의 요소가 있다면, 1번이 아닌 100번의 강제 리플로우가 발생합니다.

// 좋음: 읽기 작업을 일괄 처리한 다음 쓰기 작업을 일괄 처리함
const width = box.offsetWidth; // 단일 읽기
for (const el of elements) {
  el.style.width = width + 'px'; // 쓰기만 수행, 강제 리플로우 없음
}

한 번의 읽기, 한 번의 리플로우로 완료됩니다. 레이아웃 스래싱에 대한 web.dev 가이드에서 이 패턴을 자세히 보여줍니다. 개별 요소 크기를 읽어야 하는 경우, 먼저 모든 읽기 작업을 수집한 다음 모든 쓰기 작업을 수행하십시오.

Chrome DevTools에서 강제 리플로우 감지하기

Performance 패널을 열고 트레이스를 기록하십시오. 강제 리플로우는 플레임 차트에서 보라색 "Layout" 블록으로 표시됩니다. Chrome이 강제 동기식 레이아웃을 감지하면 빨간색 삼각형 경고를 추가합니다. 마우스를 올리면 어떤 JavaScript 줄이 리플로우를 트리거했는지 정확히 확인할 수 있습니다.

Console에도 "Forced reflow while executing JavaScript took Xms"라는 경고가 기록됩니다. 30ms를 초과하는 것은 모두 문제입니다. 단일 스크롤 이벤트 핸들러가 매 프레임마다 40ms의 레이아웃 작업을 트리거하는 사이트도 발견된 바 있습니다.

Lighthouse에서도 이를 표시합니다. Performance 카테고리의 "Avoid forced reflow(강제 리플로우 피하기)" 진단을 확인하십시오.

리플로우가 Core Web Vitals에 미치는 영향

Interaction to Next Paint (INP)

리플로우는 두 가지 방식으로 INP에 직접적인 영향을 미칩니다. 사용자가 클릭할 때 이미 강제 리플로우가 실행 중인 경우 메인 스레드가 차단되므로 입력 지연(input delay)이 증가합니다. 클릭 핸들러 자체가 레이아웃 작업을 트리거하는 경우 처리 시간(processing time)이 증가합니다. 어느 쪽이든 브라우저가 응답을 페인트하기 전에 레이아웃을 완료해야 하므로 표시 지연(presentation delay) 또한 늘어납니다.

INP의 "좋음(good)" 임계값은 200ms입니다. 단일 30ms의 강제 리플로우만으로도 이 예산의 15%를 소비하게 됩니다. 이벤트 핸들러의 레이아웃 스래싱은 INP를 500ms 이상으로 쉽게 밀어 올릴 수 있습니다.

CoreDash에서 모니터링하는 사이트들 전반에 걸쳐, DOM 읽기 및 쓰기를 일괄 처리하는 페이지는 레이아웃 스래싱 패턴이 있는 페이지에 비해 INP 점수가 약 18% 더 우수합니다.

Largest Contentful Paint (LCP)

페이지 로드 중 리플로우는 메인 스레드 시간을 두고 경쟁합니다. 폰트 로딩이 일반적인 원인입니다. 웹 폰트가 도착하여 대체(fallback) 텍스트를 교체할 때 브라우저는 해당 폰트를 사용하는 모든 요소를 리플로우합니다. 텍스트가 많은 페이지에서는 이러한 리플로우로 인해 LCP가 100ms 이상 지연될 수 있습니다.

명시적인 widthheight 속성이 없는 이미지도 동일한 문제를 일으킵니다. 2025 Web Almanac에 따르면 모바일 페이지의 62%가 여전히 크기가 지정되지 않은 이미지를 하나 이상 포함하고 있습니다. 해당 이미지가 로드되면 브라우저는 실제 크기를 수용하기 위해 페이지를 리플로우합니다.

Cumulative Layout Shift (CLS)

리플로우 자체가 CLS를 일으키지는 않습니다. CLS는 사용자가 확인한 후에 보이는 요소가 이동할 때 발생합니다. 하지만 늦게 로드되는 콘텐츠(삽입된 광고, 크기가 지정되지 않은 이미지, 동적으로 삽입된 요소)로 인한 리플로우가 대부분의 레이아웃 변경 이면의 메커니즘입니다. 리플로우 트리거를 수정하면 레이아웃 변경이 사라집니다.

레이아웃 속성을 애니메이션화하는 CSS transitions 또한 또 다른 원인입니다. height 또는 margin을 전환하면 모든 애니메이션 프레임마다 리플로우가 발생합니다.

최신 CSS를 활용한 리플로우 예방

합성 전용(Composite-only) 애니메이션

width, height, top, 또는 left 대신 transformopacity에 애니메이션을 적용하십시오. 이는 GPU 합성기(compositor) 스레드에서 실행되며 레이아웃 단계를 완전히 건너뜁니다. 요소를 이동하고 싶습니까? transform: translateX()를 사용하십시오. 시각적으로 크기를 조정하고 싶습니까? transform: scale()을 사용하십시오. 2025 Web Almanac에 따르면 모바일 페이지의 40%가 여전히 합성되지 않은 애니메이션을 사용하고 있습니다. 이는 40%의 페이지가 매 프레임마다 불필요한 레이아웃 작업을 수행하고 있음을 의미합니다.

CSS containment(격리)

contain 속성은 요소의 내부가 페이지의 나머지 부분과 독립적임을 브라우저에 알려줍니다. 격리된(contained) 요소 내부에서 무언가 변경되면 브라우저는 전체 문서 대신 해당 하위 트리(subtree)만 리플로우합니다.

article {
  contain: content;
}

이는 특히 큰 DOM이 있는 페이지에 유용합니다. 브라우저가 리플로우 중에 확인해야 할 요소가 많을수록 더 많은 시간이 걸립니다. 격리는 이 영향 범위(blast radius)를 제한합니다.

content-visibility

content-visibility: auto는 화면 밖에 있는 요소에 대해 레이아웃, 페인트 및 스타일 계산을 건너뛰도록 브라우저에 지시합니다. Google이 이를 여행 블로그 데모에서 테스트했을 때, 렌더링 시간이 232ms에서 30ms로 단축되었습니다. 7배의 성능 향상입니다.

.section {
  content-visibility: auto;
  contain-intrinsic-size: auto 500px;
}

contain-intrinsic-size는 브라우저에 자리 표시자 높이를 제공하여 스크롤바 계산이 정확하게 유지되도록 합니다. 이 속성은 2025년 9월에 Baseline Newly Available 상태가 되었으며, 이는 모든 주요 브라우저에서 작동함을 의미합니다.

실용적인 체크리스트

  1. 쓰기 작업 전에 DOM 읽기를 일괄 처리하십시오. 레이아웃 속성을 읽는 작업과 스타일 변경을 쓰는 작업을 절대 번갈아 수행하지 마십시오.
  2. 이미지 및 임베드에 명시적인 크기를 설정하십시오. 이렇게 하면 리소스가 로드될 때 리플로우를 방지할 수 있습니다.
  3. transformopacity만 사용하여 애니메이션을 적용하십시오. 이들은 레이아웃과 페인트를 완전히 건너뜁니다.
  4. 독립적인 섹션에 contain: content를 사용하십시오. 이는 리플로우를 변경된 하위 트리로만 제한합니다.
  5. 스크롤 아래(below-the-fold) 섹션에 content-visibility: auto를 추가하십시오. 이는 화면 밖 콘텐츠에 대한 레이아웃을 건너뜁니다.
  6. float보다 flexbox를 우선적으로 사용하십시오. Flexbox 레이아웃은 동일한 수의 요소에 대해 float 레이아웃보다 약 4배 더 빠릅니다.
  7. 메인 스레드에 yield하여 값비싼 DOM 작업 사이에서 페이지 응답성을 유지하십시오.
  8. 스크립트를 지연(Defer)하여 초기 렌더링이 완료될 때까지 DOM을 수정하는 작업을 미루십시오.

Real User Monitoring(RUM)을 통해 이러한 변경 사항의 실제 영향을 모니터링하십시오. Lighthouse와 같은 실험실 도구는 레이아웃 비용을 분리하여 보여주지만, 현장(field) 데이터는 사용자가 실제로 개선 사항을 경험하는지 여부를 보여줍니다.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

CoreDash엔 MCP가 기본 탑재.

Claude든 어떤 AI agent든 바로 연결. 지난주 화요일에 INP가 왜 튀었는지 그냥 물어보세요.

작동 방식 보기
브라우저 리플로우: 발생 원인 및 Core Web Vitals에 미치는 영향Core Web Vitals 브라우저 리플로우: 발생 원인 및 Core Web Vitals에 미치는 영향