WEB 브라우저 렌더링과 웹 렌더링 방식들 개념 요약 정리

작성 : 2023-09-10수정 : 2023-09-21

목차 펼치기

브라우저 렌더링 (Browser Rendering)

Google Chrome, MS Edge, Naver Whale은 Blink, Safari는 Webkit, Firefox는 Gecko 렌더링 엔진을 사용하고 있다. 엔진마다 차이는 있을 수 있으나 전반적인 브라우저 렌더링 순서는 다음과 같다.



렌더링 순서

  1. HTML 코드를 해석해 DOM Tree로 변환

  2. 스타일시트를 해석해 CSSOM Tree 생성

  3. DOM Tree와 CSSOM Tree를 결합하여 Rendering Tree 생성

    • 페이지를 렌더링 하는 데 필요한 노드만 포함한다.

    • display: none

      과 같이 숨겨진 요소는 제외된다.

  4. Layout

    • Viewport 내 각 요소의 위치와 크기 계산

    • 상대값은 이 때 고정된 픽셀로 변환된다.

    • 노드 수가 많을 수록 오래걸리고, 레이아웃 병목현상으로 스크롤이나 기타 애니메이션 동작에 렉이 발생할 수 있다.

    • 크롬 개발자도구 [Layers] 탭에서 생성된 레이어 층을 확인할 수 있다.

      • [더보기] - [More tools] - [Layers]

    • 크롬 개발자도구 [Performance] - [Event Log]에 “Layout”으로 기록되는걸 볼 수 있다.

  5. Paint

    • Rendering Tree의 각 노드를 화면의 실제 픽셀로 변환한다.

    • Layout 단계에서 계산된 결과를 이용해 화면에 그리는 과정이다.

    • 변환된 픽셀 결과를 바탕으로 여러 개의 레이어가 발생한다.

      • 일반적으로

        position

        ,

        z-index

        ,

        transform

        ,

        filter

        ,

        canvas

        ,

        video

        속성을 기준으로 나뉜다.

      • 이 레이어를 기준으로 Repaint가 발생한다.

      • wil-change

        속성을 이용해 별도로 레이어를 구성할 수 있다.

    • 페인트 레코드를 생성한다.

      페인트 레코드 : 요소의 배치 등 렌더링 순서를 기록한 정보

    • 크롬 개발자도구 [Performance] - [Event Log]에 “Paint”로 기록되는걸 볼 수 있다.

  6. Composition

    • Composite Layers를 만들어 최종 화면을 그린다.

      Composite Layers : 페인트 단계에서 생성된 레이어들의 합성


이후 특정 속성이 수정되거나 메소드가 실행될 때 Reflow 또는 Repaint 동작이 수행된다.

이 동작들은 UI 렌더링 성능에 영향을 미칠 수 있기 때문에 UX를 고려한다면 최소화해야한다.


Reflow

  • 노드의 크기나 위치가 변경되어 배치를 다시 해야할 때 발생한다.

    • Layout - Paint 과정이 다시 수행되는 것과 같다.

    • 반드시 Repaint가 따라서 발생한다.

  • 다른 노드와의 관계에 따라 추가 Reflow를 유발할 수 있어 DOM 전체에서 Reflow가 발생할 수도 있다.

  • position

    ,

    width

    ,

    height

    ,

    margin

    ,

    padding

    ,

    border

    ,

    border-width

    ,

    font-size

    ,

    font-weight

    ,

    line-height

    ,

    text-align

    ,

    overflow

    등의 속성이 변경되었을 때 발생된다.

  • clientHeight

    ,

    offsetHeight

    ,

    scrollHeight

    등의 속성이 변경되었을 때 발생된다.

  • getBoundingClientRect()

    ,

    getClientRect()

    ,

    scrollTo()

    등의 메소드가 실행될 때 발생된다.


Repaint

  • Reflow가 발생하거나 색상처럼 단순한 스타일을 변경할 때 발생한다.

    • Paint 과정을 다시 수행하는 것과 같다.

  • background

    ,

    color

    ,

    text-decoration

    ,

    border-style

    ,

    border-radius

    등의 속성이 변경되었을 때 발생된다.


렌더링 최적화

렌더링 성능을 위한 Reflow와 Repaint 최적화 방안으로는 다음과 같다.


  • 비슷한 형태의 작업은 묶어서 실행될 수 있도록 코드 순서를 배치한다.

  • 스타일 수정은 Callback Queue가 아니라 Call Stack에서 바로 실행될 수 있도록 처리한다.

    • Callback Queue는 이벤트 루프에 따라 비동기적으로 실행되어 리렌더링이 추가로 발생할 수 있다.

  • display: none

    을 활용하여 노출을 최소화 한 후 스타일을 변경하여 다시 최종 반영한다.

  • 노드를 복제하여 스타일을 수정한 후 기존 노드와 치환한다.

  • createDocumentFragment()

    함수를 통해 메인 DOM 과 별개의 DOM을 만들어 작업 후 메인 DOM에 추가한다.

  • Reflow를 유발하는 특정 속성이나 함수의 값은 별도의 변수에 저장한 후 사용한다.

    • getBoundingClientRect()

      등의 함수 같은 경우 실행될 때마다 Reflow가 발생한다.

  • 대상 노드의 개수가 많을수록 JavaScript보다 CSS 규칙을 통해 반영한다.

    • 일반적으로 JavaScript로 처리하는 것은 CSS보다 느리고 사이트렉을 유발할 수 있다.

      • 비동기 작업이나 상호작용이 필요한 요소는 JavaScript로 스타일을 수정하는 것이 적합할 수도 있다.

    • 다음과 같은 이유로 CSS로 가능한 작업들은 되도록 CSS로 진행해야 한다.

      • 최적화된 엔진

        • 브라우저는 CSS 스타일 변경에 최적화된 엔진을 가지고 있다.

        • JavaScript를 사용할 경우 JavaScript 엔진과 스타일 엔진 간 상호작용으로 처리가 지연된다.

      • 일괄 처리

        • CSS는 여러 요소에 대한 스타일 변경을 일괄 처리할 수 있어 효율적이다.

      • 메모리 사용 최적화

        • CSS는 브라우저의 스타일 시트에 스타일 규칙을 적용하기 때문에 메모리 최적화가 되어 있다.

        • JavaScript 스타일 변경 이력은 JavaScript 메모리에 저장되기 때문에 메모리 사용량이 늘어날 수 있다.

      • 구조와 스타일 분리

        • CSS를 통해 HTML과 스타일이 분리되어 가독성이 향상되고 유지보수가 쉬워진다.

        • JavaScript로 스타일을 변경하는 것은 구조와 스타일을 혼합시켜 코드 복잡도를 향상시켜 오류를 야기할 수 있다.

  • HTML 마크업을 구조적으로 최적화 한다.

    • 불필요한 wrapper가 있다면 제거한다

  • 사용하지 않는 CSS 선언은 제거한다.

    • 크롬 개발자도구 [CSS Overview] - [Unused delarations] 에서 확인할 수 있다.







웹 렌더링 방식

CSR (Client Side Rendering)

주로 SPA에서 사용되는 방식으로 동적이고 빠른 UX를 제공하는데 유리하다.


동작 방식

  1. 최초 요청 시 클라이언트가 서버로부터 최소한의 HTML을 먼저 다운로드 후 렌더링한다.

    • 최소한의 HTML이란 빈 화면의

      index.html

      수준을 말한다.

    • 주로

      <script>

      로 링크된 JavaScript 경로와

      <title>

      ,

      <meta>

      ,

      <link>

      등의 HTML 요소가 포함되어 있다.

  2. 클라이언트는 링크 된 경로를 통해 서버가 만들어둔 JavaScript 번들을 다운로드 한다. 이 번들에는 모든 렌더링 코드가 포함되어 있다. (FCP)

    • JavaScript 번들 크기에 영향을 많이 받기 때문에 코드 분할을 통해 필요할 때에 필요한 것만 제공해야 한다.

  3. 클라이언트는 JavaScript 번들을 해석해 동적으로 컨텐츠를 로드하여 렌더링을 진행하며, 이후 페이지 이동 시 이미 로드 된 JavaScript 번들을 이용해 렌더링을 진행한다. (TTI)


장점
  • 최초 페이지 요청 시 모든 페이지의 해석을 진행하기 때문에 이후 렌더링이 빠르다.

  • 서버 부하가 상대적으로 낮다.


단점
  • 최초 페이지 로드(FCP : First Contentful Paint)가 SSR에 비해 느리다.

  • 크롤링이 어려워 SEO에 불리하다.

    • 크롤러가 처음 방문했을 때 사이트에 아무 정보가 없기 때문이다.

    • 단, 구글 크롤러처럼 JavaScript를 해석할 수 있는 크롤러는 정보를 가져갈 수 있다.

  • 사용자 환경의 성능 및 호환성에 영향을 많이 받는다.


 CSR의 FCP와 TTI. 출처 :  https://developers.google.com

CSR의 FCP와 TTI. 출처 :

https://developers.google.com


SSR (Server Side Rendering)

주로 MPA에서 사용되는 방식으로 클라이언트가 서버에서 렌더링 된 결과를 다운로드 받아 사용자에게 보여주는 방식이다. 초기 페이지 로딩 성능을 향상시키고 SEO에 유리하다.


동작 방식

  1. 페이지 요청 시 서버가 데이터를 확인하고 페이지 HTML을 생성하여 클라이언트에 전송한다.

  2. 클라이언트는 다운로드 받은 HTML을 렌더링한다.(FCP)

    1. 이 때 아직은 사용자와 상호작용 할 수 없다.

  3. 클라이언트가 비동기로 다운로드 받은 JavaScript를 실행하여 DOM과 이벤트를 연결한다.(TTI)

    • React 생태계에서는 DOM과 이벤트를 연결하는 작업을 hydration이라고 한다.

  4. 페이지 이동 시 이 과정이 반복해서 실행된다.



장점
  • FCP가 빠르다.

  • 크롤링에 적합하여 SEO에 유리하다.

  • 사용자 환경의 성능에 영향을 덜 받는다.

  • 최신 데이터 갱신에 유리하다.


단점
  • 페이지 이동 시 매번 서버에서 페이지를 생성한다.

    • 만약 페이지 로드가 오래걸리면 오히려 UX를 해칠 수 있다.

    • 서버가 각 요청마다 HTML을 생성하기 때문에 CDN 수준에서 컨텐츠 캐싱이 안된다.

  • CSR에 비해 프레임워크에 대한 러닝 커브가 발생한다.

  • 서버 부하가 발생할 수 있다.


 SSR의 FCP와 TTI. 출처 :  https://developers.google.com

SSR의 FCP와 TTI. 출처 :

https://developers.google.com


SSG(Server Side Generation)

서버가 빌드 타임에 모든 페이지에 대한 HTML 파일을 생성하여 정적 파일로 관리하고 클라이언트에 전송하는 방식으로 Next.js에서 권장하는 렌더링 방식이다. CDN 등을 통해 캐싱하여 더욱 빠르게 전송할 수 있다.


장점
  • FCP, TTI(Time To Interactive)가 빠르다.

  • SEO에 유리하다.


단점
  • URL을 예측하기 힘들 경우 적용이 어렵다.

    • 모든 URL에 대해 각 HTML 파일을 생성하기 때문이다.

  • 동적 데이터 처리가 제한된다.

  • 실시간 업데이트 및 개인화 콘텐츠 제공이 제한된다.

  • 페이지가 많아질수록 빌드 과정에서 시간이 오래 걸린다.



Universal Rendering (Isomorphic Rendering)

최초 요청 시에는 SSR처럼 동작하고, 이후 CSR로 동작하는 방식이다. 서버-클라이언트 간 코드를 공유하며, 이 때 양측에 동일한 결과를 보장하는 코드를 isomorphic 하다고 하여 Isomorphic Rendering이라고도 한다.


Next.js에서 지원하고 있는데 URL로 직접 접근하면 SSR로 렌더링 되고, 이후 Link 컴포넌트 등을 통해 내부 로직으로 라우팅 할 경우 SPA로 렌더링 되는 모습을 볼 수 있다.


동작 방식
  1. 서버는 초기 요청에 대한 HTML을 클라이언트에 전송한다.

  2. 클라이언트는 서버에서 다운로드 받은 HTML을 렌더링한다.

  3. 클라이언트는 비동기로 다운로드 받은 JavaScript 번들을 해석하여 DOM과 이벤트를 연결한다.

  4. 이후 페이지 이동 시에는 이미 로드 된 JavaScript 번들을 이용해 페이지를 렌더링한다.


장점
  • SSR로 초기 페이지 로딩이 빠르다.

  • SSR로 SEO에 유리하다.

  • CSR로 빠르고 부드러운 UX를 제공한다.


단점
  • 프레임워크에 대한 러닝 커브가 발생한다.

  • 서버-클라이언트 간 코드를 공유하고 동일한 렌더링 로직을 사용해 복잡성이 증가한다.

  • 상호작용은 페이지가 로딩 된 후 클라이언트 측에서 처리되도록 고려되어야 한다.

  • 서버 부하가 발생할 수 있다.



ISR (Incremental Static Regeneration)

SSG을 확장시킨 방식으로 전체 사이트를 다시 빌드하지 않아도 페이지 단위로 동적 데이터를 반영하여 정적 페이지를 업데이트 할 수 있다. Next.js에서 지원하고 있다.


동작 방식
  1. 최초 요청 시 캐싱 된 페이지가 있다면 바로 전달한다.

    • 캐싱 되지 않은 페이지일 경우 서버가 렌더링하여 캐싱 후 전달한다.

    • 캐싱은 Stale-while-revalidate 전략을 사용한다.

  2. 특정 주기나 이벤트에 따라 백그라운드에서 페이지 재생성 트리거가 실행된다.

  3. 재생성에 성공할 경우 기존 캐시를 무효화하고 업데이트된 페이지를 표시한다.

    • 실패할 경우 기존 페이지는 영향 받지 않는다.


장점
  • 초기 페이지 로딩이 빠르다.

  • 정적 페이지를 제공하면서도 동적으로 데이터를 제공할 수 있다.


단점
  • 구현 및 관리가 복잡하다.

  • 페이지 재생성 트리거와 캐싱 및 업데이트 로직들로 인해 복잡도가 증가한다.



Wanna get in touch?

All Icons byiconiFy