Critical Rendering Path

2024. 11. 13. 15:42·Web
반응형

HTML의 렌더링 과정에 대해서 지난번에 공부한 적이 있다. 

간단하게는 다음과 같은 과정을 거치게 된다. 

  1. 다운로드 : 화면을 그려주는데 필요한 리소스(html, css, js)를 다운로드 한다. 
  2. HTML 준비 : 렌더링 되어야 할 HTML 요소로 DOM을 만들어 준다. 
  3. CSS 준비 : css 코드를 가지고 와서 CSSOM을 만들어 준다. 
  4. 두개 합치기 : 둘을 합쳐서 렌더링 트리를 생성한다. 
  5. 위치 그리기 : 화면에 요소들이 어디 놓일 지 그려준다. 
  6. 색칠하기 : 그려친 요소에 색을 칠해준다. 

여기서 위치 그리기, Layout 단계가 다시 실행되면 색칠하기, Paint 단계도 다시 실행되는 Reflow는 성능 저하의 주요 원인이 된다. 

 

메모이제이션을 통해 불필요한 기능 재실행을 줄이는 것 외에도, reflow 발생을 줄이거나 사전 로드(prefetch, preload) 기능을 통해 필요한 데이터를 미리 받아와 성능을 개선할 수 있다. 

 

이번 글에서는 이러한 최적화 기법을 사용해 어떻게 브라우저 성능을 최적화할 수 있는지 살펴보려고 한다. 

Reflow

게시글 데이터를 가져오는 상황을 가정해보자. 목록을 조회할 때 데이터를 불러오는 동안 UI상에 빈 공간이 나타났다가 데이터가 로드되면 해당 영역이 다시 렌더링된다.

import { useEffect, useState } from "react";

const getData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        {
          id: 1,
          title: "1",
          content: "content",
        },
        {
          id: 2,
          title: "2",
          content: "content",
        },
        {
          id: 3,
          title: "3",
          content: "content",
        },
        {
          id: 4,
          title: "4",
          content: "content",
        },
        {
          id: 5,
          title: "5",
          content: "content",
        },
        {
          id: 6,
          title: "6",
          content: "content",
        },
        {
          id: 7,
          title: "7",
          content: "content",
        },
        {
          id: 8,
          title: "8",
          content: "content",
        },
      ]);
    }, 1000);
  });
};

const Crp = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    getData().then((data) => setData(data));
  }, []);

  return (
    <>
      {data.map(({ id, title, content }) => (
        <div key={id}>
          <p>{title}</p>
          <p>{content}</p>
        </div>
      ))}

      <div
        style={{
          width: "100%",
          height: "100px",
          background: "red",
        }}
      >
        Footer
      </div>
    </>
  );
};

export default Crp;

예제 코드를 보면, setTimeout을 사용하여 인터넷 속도가 느려지는 상황을 시뮬레이션하고, 1초 뒤에 게시글 데이터를 화면에 렌더링하도록 설정했다. 

 

데이터가 늦게 로드되면 Footer로 예상되는 요소가 게시글 데이터가 화면에 나타나면서 아래로 밀리는 것을 확인할 수 있다. 성능이 좋은 환경에서는 문제가 덜하겠지만, 모바일 환경이나 인터넷 속도가 느린 경우 layout shift가 발생해 UX가 나빠지고, 잦은 reflow로 성능 저하가 발생할 수 있다. 

 

이를 해결하기 위해 데이터 로드 전, 미리 placeholder로 자리 크기를 지정해둔다면, reflow 없이 안정적인 레이아웃이 유지 된다. 예를 들어 아래와 같이 height를 미리 지정하여 데이터 로딩 중 요소의 위치를 유지할 수 있다. 

{(data ?? new Array(8).fill(1)).map(({ id, title, content }) => (
<div
    key={id}
    style={{
    height: "58px",
    }}
>
    <p>{title}</p>
    <p>{content}</p>
</div>
))}

이처럼 placeholder 영역을 설정하면 브라우저가 페이지를 불필요하게 다시 그리는 것을 방지할 수 있어 성능 개선과 안정적인 UX에 기여하게 된다. 

 

prefetch

prefetch란 사용자가 다음에 이동할 가능성이 높은 페이지나 리소스를 미리 받아두는 것을 의미한다. 현재 페이지의 로드가 완료된 후 우선순위가 낮은 리소스를 처리하며, 예를 들어 아래와 같이 prefetch 속성을 추가하여 다음 페이지를 미리 다운로드할 수 있다. 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>프리페치</title>

    <!-- 프리페치: 다음페이지를 미리 다운로드 받으므로, 버튼 클릭시 페이지이동 빠름 -->
    <link rel="prefetch" href="board.html" />
  </head>
  <body>
    <a href="board.html">게시판으로 이동하기</a>
  </body>
</html>
<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>게시판</title>
  </head>
  <body>
    여기는 게시판입니다
  </body>
</html>

이를 통해 사용자가 링크를 클릭하여 다음 페이지로 이동할 때 로드 시간이 거의 없어 빠르게 페이지가 나타난다. 예를 들어, index.html에서 board.html을 prefetch하면 개발자 도구의 Network 탭에서 'Size: prefetch cache'로 표시되어, 캐시에 저장된 리소스를 로드하지 않아도 되는 것을 확인할 수 있다. 

 

이처럼 prefetch는 UX 개선에 매우 유용하지만, 사용자가 해당 페이지로 이동하지 않을 경우 불필요한 리소스 사용이 발생할 수 있다. 따라서 링크에 마우스를 올렸을 때 prefetch하도록 설정하는 것도 좋은 방법이다. 상황에 따라 적절히 사용하는 것이 중요하다. 

 

preload

preload란 페이지가 로드될 때 필요한 리소스(이미지, 폰트, 스크립트 등)을 우선적으로 다운로드하여 페이지 로드 속도를 개선하는 방법이다. 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>프리로드</title>

    <link rel="stylesheet" href="./index.css" />
    <script src="index1.js"></script>
    <script src="index2.js"></script>
    <script src="index3.js"></script>
    <script src="index4.js"></script>
    <script src="index5.js"></script>
    <script src="index6.js"></script>
  </head>
  <body>
    <img src="./dog.jpeg" />
  </body>
</html>

HTML 1.1 버전에서는 일반적으로 최대 6개의 리소스를 병렬로 다운로드하므로, 강아지 이미지가 다운로드 순서에서 밀려서 사용자가 해당 이미지를 보기까지 기다려야 할 수 있다. 

실제 결과에서도 추측했던 것과 동일하게 데이터를 불러오고 있다. 

 

하지만 강아지 이미지는 사용자에게 먼저 보이는 부분이기 때문에 다른 요소보다 먼저 로드가 된다면 홈페이지를 접속했을 때 화면이 변경되는 문제가 적어질 것이다. 이때 사용하는 것이 preload이다. 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>프리로드</title>

    <link rel="preload" as="image" href="./dog.jpeg" />

    <link rel="stylesheet" href="./index.css" />
    <script src="index1.js"></script>
    <script src="index2.js"></script>
    <script src="index3.js"></script>
    <script src="index4.js"></script>
    <script src="index5.js"></script>
    <script src="index6.js"></script>
  </head>
  <body>
    <img src="./dog.jpeg" />
  </body>
</html>

preload의 장점은 사용자에게 시각적으로 중요한 리소스를 빠르게 로드해 초기 화면 표시 속도를 높일 수 있다는 점이다. 다만, 모든 리소스에 preload를 사용하는 것은 브라우저 성능에 오히려 부정적인 영향을 줄 수 있으므로, 정말 우선 로드가 필요한 리소스에만 사용하는 것이 좋다.

반응형
저작자표시 비영리 변경금지 (새창열림)

'Web' 카테고리의 다른 글

iPad 클론코딩  (3) 2024.11.23
CORS  (1) 2024.11.02
단방향 암호화와 양방향 암호화  (1) 2024.10.05
웹사이트 로그인의 역사  (1) 2024.10.02
3D 애니메이션 심화  (2) 2024.09.08
'Web' 카테고리의 다른 글
  • iPad 클론코딩
  • CORS
  • 단방향 암호화와 양방향 암호화
  • 웹사이트 로그인의 역사
잉여개발자
잉여개발자
풀스택 개발자를 목표로 잉여롭게 개발 공부도 하면서 다양한 취미 생활도 즐기고 있는 잉여 개발자입니다.
  • 잉여개발자
    잉여로운 개발일지
    잉여개발자
    • 분류 전체보기 (789)
      • 개발정보 (36)
      • 개발환경 (7)
      • 개발생활 (19)
      • React (141)
        • 이론 (23)
        • 기능 (12)
        • 실험실 (88)
        • 버그 (6)
        • 패스트캠퍼스 (9)
        • Npm (3)
      • React Native (28)
        • 공통 (6)
        • TypeScript (3)
        • JavaScript (18)
        • 버그 (1)
      • Next.js (30)
        • 이론 (13)
        • 실험실 (13)
        • 버그 (3)
      • Web (35)
      • 알고리즘 (202)
        • 풀이 힌트 (39)
      • JavaScript (47)
      • TypeScript (29)
        • 기초 (27)
        • 실험실 (2)
      • Node.js (13)
        • 이론 (0)
        • 기능 (3)
        • 실험실 (9)
        • 버그 (1)
      • 도커 (4)
      • CCNA (22)
        • 이론 (4)
        • 문제 (18)
      • 취미생활 (167)
        • 잉여로운 칵테일 (2)
        • 잉여의 식물키우기 (130)
        • 잉여로운 여행기 (11)
        • 잉여의 제2외국어 (21)
        • 잉여로운 책장 (2)
      • Java (1)
        • Java의 정석 (1)
      • 꿀팁 공유 (3)
  • 태그

    javascript
    바질 키우기
    리액트
    영어독학
    next.js
    react
    다이소
    Node.js
    ChatGPT
    typescript
    CCNA
    redux
    Babel
    알고리즘
    타일러영어
    Docker
    네트워크
    프로그래머스
    리얼학습일기
    webpack
    ReactNative
    CSS
    자바스크립트
    덤프
    식물
    리얼클래스
    타입스크립트
    네이버 부스트캠프
    바질
    영어회화
  • hELLO· Designed By정상우.v4.10.1
잉여개발자
Critical Rendering Path
상단으로

티스토리툴바