HTML의 렌더링 과정에 대해서 지난번에 공부한 적이 있다.
간단하게는 다음과 같은 과정을 거치게 된다.
- 다운로드 : 화면을 그려주는데 필요한 리소스(html, css, js)를 다운로드 한다.
- HTML 준비 : 렌더링 되어야 할 HTML 요소로 DOM을 만들어 준다.
- CSS 준비 : css 코드를 가지고 와서 CSSOM을 만들어 준다.
- 두개 합치기 : 둘을 합쳐서 렌더링 트리를 생성한다.
- 위치 그리기 : 화면에 요소들이 어디 놓일 지 그려준다.
- 색칠하기 : 그려친 요소에 색을 칠해준다.
여기서 위치 그리기, 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 |