기타

브라우저 랜더링 과정 - CRP(Critical Rendering Path)

인어공쭈 2024. 9. 17. 10:49

 

브라우저가 웹 페이지를 표시하는 과정은 네트워크 관점과 렌더링 관점에서 나눠서 설명할 수 있다.

 

 

  • 네트워크 관점에서는 브라우저가 서버로부터 필요한 리소스를 요청하고 다운로드하는 과정
  • 렌더링 관점에서는 다운로드한 리소스를 사용해 브라우저가 화면에 웹 페이지를 그리는 과정

 

네트워크 관점

웹 페이지를 로드하는 과정은 브라우저가 서버로부터 필요한 리소스를 다운로드하는 네트워크 통신 단계입니다. 주요 단계는 다음과 같습니다:

1) URL 입력 및 DNS 조회

사용자가 웹 브라우저 주소창에 URL을 입력하면, 브라우저는 먼저 DNS 조회를 통해 해당 URL의 도메인 이름을 IP 주소로 변환합니다. 이 IP 주소는 웹 서버가 위치한 곳을 나타냅니다.

2) TCP 연결 및 SSL 핸드셰이크

브라우저는 서버와 TCP 연결을 맺습니다. 웹사이트가 HTTPS를 사용하는 경우, SSL/TLS 핸드셰이크를 통해 암호화된 통신 채널을 설정합니다.

3) HTTP 요청

TCP 연결이 완료되면 브라우저는 서버에 HTTP(S) 요청을 보냅니다. 이 요청은 사용자가 방문하려는 페이지에 대한 정보(예: HTML 파일)를 요구합니다. 서버는 이 요청을 받고, 응답으로 HTML 문서를 브라우저에 보냅니다.

4) 리소스 요청 (CSS, JS, 이미지 등)

HTML 문서를 받은 후, 브라우저는 추가적인 리소스(CSS 파일, 자바스크립트 파일, 이미지 등)가 필요한지 파악합니다. 이를 위해 HTML 문서를 파싱하여 해당 리소스들에 대한 추가 HTTP 요청을 서버에 보냅니다.

5) 응답 처리

서버로부터 받은 모든 리소스는 브라우저의 캐시에 저장되고, 이를 바탕으로 렌더링을 시작합니다. 네트워크 관점에서 중요한 점은 요청 및 응답이 비동기로 이루어진다는 점입니다. 즉, HTML 문서를 읽는 동안에도 다른 리소스를 병렬적으로 다운로드할 수 있습니다.

 

더보기

**CRP(Critical Rendering Path)**는 렌더링 과정 중에서도 최대한 빠르게 웹 페이지가 사용자에게 표시되는 데 중요한 단계들에 초점을 맞춘 개념이다. 즉, CRP는 초기 로드 성능을 최적화하기 위한 과정에 중점을 둔다.

 

CRP의 주요 단계

 

CRP는 브라우저가 웹 페이지를 렌더링할 때 거치는 필수적인 단계를 포함한다. 각 단계는 다음과 같다.

1. HTML 파싱과 DOM 트리 생성

브라우저가 서버에서 HTML 파일을 받아오면 이를 파싱하여 DOM(Document Object Model) 트리를 생성한다. DOM은 웹 페이지의 각 요소를 노드로 변환한 트리 구조로, HTML 문서의 논리적인 표현이다. <html>, <body>, <div> 같은 태그들이 트리의 각 노드로 변환된다.

이 과정에서 중요한 점은, 만약 <script> 태그를 만나면 DOM 생성이 중단된다는 것이다. JavaScript는 DOM을 수정할 수 있기 때문에, 먼저 실행되어야 하기 때문이다.

2. CSS 파싱과 CSSOM 트리 생성

HTML 파싱이 진행되는 동시에, CSS 파일도 파싱되어 CSSOM(CSS Object Model) 트리가 생성된다. CSSOM은 HTML의 스타일 정보를 표현한 트리이다. 이 단계에서 브라우저는 스타일 규칙을 DOM 요소에 적용할 준비를 한다.

CSS 파일이 모두 로드되고 파싱되기 전까지는 브라우저가 렌더링을 시작하지 않는다. CSS는 웹 페이지의 레이아웃을 결정하는 중요한 요소이기 때문이다.

3. 렌더 트리 생성

브라우저는 DOM 트리와 CSSOM 트리를 결합하여 렌더 트리(Render Tree)를 생성한다. 렌더 트리는 화면에 실제로 표시될 요소들만 포함한다. 예를 들어, display: none;으로 설정된 요소는 렌더 트리에 포함되지 않는다.

이 렌더 트리는 브라우저가 화면에 무엇을 표시할지 결정하는 역할을 한다.

4. 레이아웃 계산

렌더 트리가 완성되면 브라우저는 각 요소가 화면에서 어느 위치에 배치될지, 그리고 그 요소의 크기가 얼마나 되는지를 계산한다. 이 과정을 레이아웃 단계라고 하며, ‘reflow’라고도 불린다.

레이아웃 계산은 웹 페이지의 각 요소가 화면에 어떻게 배치될지를 정하는 과정으로, 사용자가 실제로 보는 레이아웃을 결정하는 중요한 단계이다.

5. 페인팅 (Painting)

레이아웃이 완료되면, 브라우저는 화면에 요소들을 실제로 그리기 시작한다. 이 단계를 페인팅이라고 하며, 각 요소의 시각적 속성(색상, 배경, 텍스트 등)을 화면에 표시한다.

이 과정은 픽셀 단위로 화면에 그림을 그리는 단계로, 사용자가 실제로 웹 페이지를 보는 시점이다.

6. 컴포지팅 (Compositing)

브라우저는 마지막으로 각 요소를 레이어로 분리하고, 이 레이어들을 합성하여 화면에 표시한다. 복잡한 애니메이션이나 3D 변환은 별도의 레이어에서 처리되며, 이러한 레이어들이 하나로 결합되어 최종 화면이 렌더링된다.

 

더보기

DOM 트리와 렌더 트리의 차이

DOM 트리 (Document Object Model)

DOM 트리는 브라우저가 HTML 문서를 파싱하여 생성하는 트리 구조로, HTML 문서의 모든 요소를 포함한다. HTML의 각 태그는 DOM 트리의 노드가 되며, 이 노드를 통해 자바스크립트나 CSS가 웹 페이지를 조작할 수 있다. DOM 트리에는 보이지 않는 요소들도 포함되며, HTML 문서 전체를 나타낸다.

렌더 트리 (Render Tree)

렌더 트리는 브라우저가 DOM 트리와 CSSOM(CSS Object Model) 트리를 기반으로 생성하는 트리이다. 렌더 트리는 화면에 실제로 표시될 요소들만 포함한다. display: none; 같은 속성으로 화면에 표시되지 않는 요소들은 렌더 트리에 포함되지 않는다. 렌더 트리는 각 요소가 어떻게 그려질지에 대한 정보(위치, 크기, 스타일)를 바탕으로 만들어진다.

차이점

  • DOM 트리는 HTML 문서의 모든 요소를 포함하며, 웹 페이지의 구조와 내용을 정의한다.
  • 렌더 트리는 화면에 실제로 표시될 요소들만 포함하며, 요소들의 스타일과 레이아웃 정보를 바탕으로 페이지를 렌더링하는 데 사용된다.

CRP 최적화 방법

CRP를 최적화하는 것은 페이지 로딩 시간을 단축하고, 더 나은 사용자 경험을 제공하기 위해 중요하다. 이를 위해 몇 가지 방법이 있다.

1. CSS와 JavaScript 최적화

CSS와 JavaScript는 렌더링을 차단할 수 있다. 이를 해결하기 위해 CSS 파일을 최소화하고 병합하여 로딩 시간을 단축해야 한다. 또한, JavaScript는 비동기적으로 로드하여 렌더링을 막지 않도록 해야 한다. async 또는 defer 속성을 사용하면 JavaScript 로딩으로 인해 렌더링이 지연되는 것을 방지할 수 있다.

2. 리소스 요청 최소화

페이지 로딩 속도를 개선하려면 중요한 리소스는 우선적으로 로드하고, 그렇지 않은 리소스는 나중에 로드하는 것이 좋다. 예를 들어, 이미지나 폰트 같은 리소스는 lazy-loading을 사용해 필요할 때만 로드할 수 있다. 또한, CDN(Content Delivery Network)을 사용하여 리소스 로딩 속도를 높일 수 있다.

3. 페인트 성능 향상

CSS 애니메이션이나 복잡한 스타일링은 브라우저의 페인트 성능에 영향을 미친다. 복잡한 레이아웃이나 스타일링을 피하고, GPU 가속을 사용하는 CSS 속성(예: transform, opacity)을 사용하면 성능을 크게 향상시킬 수 있다.

 

더보기

defer와 async 속성의 차이

async

async 속성은 스크립트가 비동기적으로 로드되도록 한다. 스크립트를 비동기로 로드하면, 브라우저는 HTML 파싱을 계속 진행하면서 스크립트를 다운로드하고, 다운로드가 완료되면 즉시 실행한다. 이 과정에서 HTML 파싱과 스크립트 실행이 병렬로 진행되지만, 스크립트 실행 순서는 보장되지 않는다. 즉, async 스크립트는 먼저 다운로드가 완료된 스크립트가 먼저 실행된다.

  • 장점: 페이지 로딩 시간을 줄일 수 있다.
  • 단점: 스크립트 실행 순서가 중요할 경우 적합하지 않다.

defer

defer 속성은 스크립트가 비동기적으로 로드되지만, HTML 파싱이 완료된 후에 스크립트가 실행되도록 보장한다. 즉, HTML 문서가 완전히 파싱된 후에 스크립트가 실행되며, 여러 defer 스크립트가 있을 경우 순차적으로 실행된다. defer는 script 태그를 body 하단에 두는 것과 비슷한 효과를 내지만, HTML 파싱 중에도 스크립트를 비동기적으로 다운로드하므로 전체 로딩 속도를 더 최적화할 수 있다.

  • 장점: 스크립트 실행 순서를 보장하면서 HTML 파싱을 방해하지 않는다.
  • 단점: 단순한 스크립트 로딩에는 async보다 덜 효율적일 수 있다.

차이점 정리

  • async: 스크립트가 HTML 파싱과 병렬로 로드되며, 다운로드가 완료되면 즉시 실행된다. 스크립트 실행 순서는 보장되지 않는다.
  • defer: 스크립트가 비동기로 로드되지만, HTML 파싱이 끝난 후 순차적으로 실행된다.

 

 

** 랜더링 과정중에 추가적인 현상들 **

 

1) 리플로우(Reflow) & 리페인트(Repaint)란?

리플로우는 브라우저가 웹 페이지의 레이아웃을 다시 계산하는 과정이다. 웹 페이지의 구조나 스타일이 변경되어 요소들의 위치나 크기를 재계산해야 할 때 리플로우가 발생한다. 리플로우는 DOM 트리와 CSSOM 트리를 바탕으로 다시 레이아웃을 계산하는 작업으로, CPU와 메모리 자원이 많이 사용되기 때문에 성능에 영향을 미친다.

 

리페인트는 요소의 시각적 스타일이 변경되어 화면에 다시 그려야 할 때 발생하는 과정이다. 예를 들어, 요소의 배경색, 글자 색상 등이 변경되면 리페인트가 일어난다. 이 과정에서는 요소의 크기나 위치가 변경되지 않고 시각적인 변화만 화면에 반영된다. 리플로우보다는 자원 소모가 적지만, 빈번한 리페인트 역시 성능 저하를 일으킬 수 있다.

리플로우는 언제 일어날까?

  • 요소의 크기나 위치가 변경될 때 (예: width, height, padding, margin 등).
  • DOM 구조가 변경될 때 (예: 요소 추가, 삭제).
  • 브라우저 창의 크기를 조절할 때 (창 크기에 따라 레이아웃이 달라지는 경우).
  • 폰트 크기가 변경되었을 때.

리페인트는 언제 일어날까?

  • 요소의 시각적 속성이 변경될 때 (예: color, background-color, visibility 등).
  • 단순한 스타일 변화가 발생할 때 (레이아웃이 변경되지 않는 시각적 속성).

 

2) script 태그를 body 하단에 위치시키는 이유

script 태그를 body 태그 하단에 위치시키는 이유는 페이지 로딩 성능을 향상시키기 위함이다.

브라우저는 HTML 문서를 위에서부터 아래로 순차적으로 파싱한다. 만약 script 태그가 페이지의 상단에 위치해 있으면, 브라우저는 자바스크립트를 실행하기 위해 HTML 파싱을 중단하고, 스크립트를 다운로드하고 실행한 후에 다시 파싱을 진행하게 된다. 이로 인해 페이지 로딩이 지연될 수 있다.

반면, script 태그를 body의 하단에 위치시키면 브라우저는 먼저 HTML을 모두 파싱하고 DOM 트리를 완성한 후에 자바스크립트를 실행하게 된다. 이 방식은 사용자에게 더 빠르게 콘텐츠를 표시할 수 있도록 돕는다.

 

3) 강제 동기 레이아웃(Forced Synchronous Layout)

강제 동기 레이아웃은 브라우저가 레이아웃 계산을 즉시 강제로 수행해야 하는 상황을 의미한다. 일반적으로 브라우저는 레이아웃 작업을 효율적으로 수행하기 위해 한 번에 여러 요소를 처리하려고 한다. 하지만 JavaScript가 DOM을 조작하면서 브라우저의 최신 레이아웃 정보를 필요로 하는 경우, 브라우저는 그 즉시 레이아웃을 다시 계산하게 된다. 이 상황을 강제 동기 레이아웃이라고 부른다.

강제 동기 레이아웃이 발생하는 경우

강제 동기 레이아웃은 JavaScript 코드에서 DOM의 레이아웃 정보를 요청할 때 발생한다. 예를 들어, JavaScript에서 아래와 같은 레이아웃 정보에 접근하면 브라우저는 최신 레이아웃을 계산해야 한다.

  • offsetWidth, offsetHeight
  • scrollTop, scrollHeight
  • getComputedStyle()

이러한 속성이나 메서드를 호출할 때 브라우저는 즉시 DOM을 다시 계산하여 최신 레이아웃 정보를 제공해야 하므로 강제로 레이아웃 계산을 동기적으로 실행한다. 이로 인해 성능이 저하될 수 있다.

성능에 미치는 영향

강제 동기 레이아웃은 DOM을 조작하는 코드의 흐름을 방해하며, 특히 DOM을 자주 읽고 쓸 때 성능 저하를 일으킬 수 있다. 이러한 현상이 누적되면 웹 페이지가 느려지고, 렌더링이 지연될 수 있다.

 

4) 레이아웃 쓰레싱(Layout Thrashing)

레이아웃 쓰레싱DOM을 반복적으로 읽고 쓰는 작업이 교차하면서 브라우저가 계속해서 레이아웃을 재계산하는 현상을 말한다. 이는 성능 저하의 주요 원인 중 하나로, 특히 반복적으로 DOM의 레이아웃 정보를 읽고 그에 따라 DOM을 수정하는 상황에서 발생한다.

레이아웃 쓰레싱이 발생하는 과정

레이아웃 쓰레싱은 JavaScript 코드가 DOM을 자주 읽고 쓰는 작업을 섞어서 수행할 때 발생한다. 예를 들어:

  1. DOM 요소의 레이아웃 속성을 읽음 (예: element.offsetHeight).
  2. 그 값을 기반으로 DOM의 스타일을 수정함 (예: element.style.height = '100px').
  3. 다시 DOM 요소의 레이아웃 속성을 읽음 (예: element.offsetWidth).

이런 흐름이 반복되면 브라우저는 각 DOM 수정마다 레이아웃을 다시 계산해야 하며, 이를 통해 레이아웃 쓰레싱이 발생하게 된다. 이는 CPU와 메모리 자원을 과도하게 사용하여 성능 저하를 일으킨다.

레이아웃 쓰레싱 해결 방법

레이아웃 쓰레싱을 방지하기 위한 일반적인 방법은 DOM 읽기와 쓰기 작업을 구분하는 것이다. 즉, DOM에서 레이아웃 정보를 읽는 작업을 먼저 모두 수행한 후, DOM을 수정하는 작업을 별도로 처리하는 방식이다.

 

예시)

잘못된 방식 (레이아웃 쓰레싱 발생 가능):
element.style.width = element.offsetWidth + 10 + 'px';
element.style.height = element.offsetHeight + 10 + 'px';

개선된 방식 (레이아웃 쓰레싱 방지):
const width = element.offsetWidth;
const height = element.offsetHeight;

element.style.width = width + 10 + 'px';
element.style.height = height + 10 + 'px';

레이아웃 쓰레싱의 성능 영향

레이아웃 쓰레싱은 성능 저하를 유발하며, 특히 반복적인 DOM 조작을 할 때 큰 문제가 된다. 쓰레싱을 줄이면 웹 페이지의 성능을 크게 개선할 수 있다.

따라서, 레이아웃 쓰레싱을 피하려면 DOM에 접근하거나 조작할 때 최적화된 방식으로 처리하는 것이 중요하다.

 

5) Render Blocking

Render Blocking은 브라우저가 HTML을 파싱할 때, 특정 리소스(특히 CSS나 JavaScript)가 로드될 때까지 렌더링을 중단하는 현상을 말한다. 렌더링 중단이 발생하면, 웹 페이지가 사용자에게 늦게 표시되거나, 불완전한 상태로 표시될 수 있다. 이러한 현상을 막기 위해 다양한 최적화 방법이 있다.

Render Blocking을 막기 위한 방법들:

  1. CSS의 최적화
    • CSS 파일 최소화: 불필요한 공백이나 주석을 제거하여 파일 크기를 줄인다.
    • CSS 파일 병합: 여러 개의 CSS 파일을 하나로 병합해 HTTP 요청 수를 줄인다.
    • 필요한 CSS만 로드: 페이지마다 필요한 CSS만 로드하고, 불필요한 CSS는 나중에 로드하거나 제거한다.
    • Critical CSS 인라인화: 페이지의 주요 스타일(critical CSS)을 HTML 내부에 직접 인라인화하여, 초기 렌더링을 방해하지 않게 한다.
  2. JavaScript의 최적화
    • defer 속성 사용: script 태그에 defer 속성을 추가하여, HTML 파싱이 완료된 후에 JavaScript가 실행되도록 한다.
    • async 속성 사용: async 속성을 사용해 JavaScript를 비동기적으로 로드하고, 렌더링을 차단하지 않도록 한다.
    • 필요한 JavaScript만 로드: 페이지마다 필요한 스크립트만 로드하고, 나머지는 나중에 로드하거나 lazy-loading을 활용한다.
  3. 리소스 로딩 최적화
    • 리소스 압축: HTML, CSS, JavaScript 파일을 Gzip이나 Brotli와 같은 압축 방식으로 전송하여 로딩 속도를 높인다.
    • HTTP/2 사용: HTTP/2 프로토콜을 사용하면 한 번의 연결로 여러 리소스를 동시에 다운로드할 수 있어 렌더링 차단을 줄일 수 있다.

 

6) CLS

CLS (Cumulative Layout Shift)는 웹 페이지가 로딩되는 동안 시각적 요소가 예기치 않게 이동하는 현상을 측정하는 지표이다. 사용자가 웹 페이지를 보는 중에 버튼, 이미지, 텍스트 등 시각적 요소가 갑작스럽게 위치를 변경하면, 사용성에 큰 영향을 미칠 수 있다. CLS는 이러한 레이아웃 이동이 얼마나 발생했는지에 대한 점수를 나타낸다.

CLS 개선을 위한 방법:

  1. 이미지 크기 명시: 이미지를 삽입할 때 width와 height 속성을 명시하여, 이미지가 로드되기 전에도 공간을 차지하게 만들어 레이아웃 이동을 방지한다.
  2. 비디오 크기 명시: 비디오 요소도 마찬가지로 크기를 미리 설정해 예기치 않은 레이아웃 이동을 막는다.
  3. 동적으로 삽입되는 콘텐츠 제어: 광고나 팝업처럼 동적으로 삽입되는 요소는 CSS에서 고정된 공간을 할당하거나, 로딩 후 레이아웃이 변경되지 않도록 한다.
  4. 웹 폰트 최적화: 웹 폰트 로드 중 텍스트가 갑자기 다른 폰트로 바뀌면서 레이아웃이 이동하는 문제를 방지하기 위해, font-display: swap 속성을 활용해 텍스트가 일시적으로 시스템 폰트로 표시되도록 한다.

 

7) FOUT & FOIT

FOUT (Flash of Unstyled Text)

FOUT는 웹 페이지 로딩 중 웹 폰트가 로드되기 전, 텍스트가 시스템 폰트로 잠깐 표시되는 현상이다. 웹 폰트가 완전히 로드되면 텍스트는 다시 지정된 폰트로 변경된다. FOUT는 사용자가 페이지 로딩 중에도 텍스트를 볼 수 있게 하지만, 폰트가 바뀌는 시각적 변화가 발생한다.

FOIT (Flash of Invisible Text)

FOIT는 웹 페이지 로딩 중 웹 폰트가 로드될 때까지 텍스트가 보이지 않는 현상이다. 폰트가 완전히 로드될 때까지 텍스트는 보이지 않다가, 폰트가 로드되면 한 번에 표시된다. 이로 인해 사용자는 텍스트가 늦게 나타나는 것처럼 느낄 수 있다.

웹 폰트를 빠르게 로드하기 위한 방법:

  1. font-display 속성 사용: font-display CSS 속성을 사용하면 텍스트의 로딩 방식을 제어할 수 있다. 예를 들어, font-display: swap을 사용하면 웹 폰트가 로드되기 전 시스템 폰트를 먼저 표시하고, 웹 폰트가 로드되면 그 폰트로 교체한다. 이를 통해 FOIT를 방지하고 FOUT를 완화할 수 있다.
  2. 서버 폰트 최적화: 웹 폰트를 서빙하는 서버를 최적화하여 폰트 로딩 속도를 높인다. CDN을 활용하거나 HTTP/2를 사용하면 여러 리소스를 동시에 빠르게 다운로드할 수 있다.
  3. 웹 폰트 파일 크기 줄이기: 웹 폰트 파일을 서브셋팅하여 실제 필요한 글자만 포함하고, 불필요한 글리프나 언어 파일을 제거하여 폰트 크기를 줄인다.
  4. Preload 사용: <link rel="preload"> 태그를 사용해 웹 폰트를 미리 로드하여 폰트 로딩 시간을 단축할 수 있다. 이를 통해 사용자가 처음 페이지를 열었을 때 폰트가 빠르게 적용될 수 있다.

 

반응형

'기타' 카테고리의 다른 글

HTTPS & HTTP 네트워크  (2) 2024.09.17
Swiper.js 사용법 및 여러개 사용시 버그 해결  (0) 2024.07.11
vscode Prettier 설정  (0) 2024.04.18
구글 계정 로그인 구현 (OAuth)  (0) 2023.06.22
웹팩(WebPack)이란?  (0) 2023.05.22