사용자에게 최대한 빨리 화면을 보여주고, 최대한 빨리 앱의 기능을 제공하는 방법을 알아봅시다.
우측 문서의 번역입니다. : https://web.dev/critical-rendering-path-analyzing-crp/
이하 예제를 설명하기 위한 가정
- 리소스(CSS, JS 또는 HTML 파일)를 처리할 수 있게 된 후 브라우저에서 일어나는 일에만 집중합니다.
- 캐시나 네트워크에서 리소스를 가져오는 데 걸리는 시간은 무시합니다.
- 서버로의 네트워크 왕복(전파 대기 시간;propagation latency) 비용(시간)은 100ms입니다.
- 서버의 응답 시간은 HTML 문서의 경우 100ms이고 다른 모든 파일의 경우 10ms입니다.
Hello world 예제
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
간단한 HTML 마크업과 단일 이미지로 시작하겠습니다. CSS 또는 JavaScript가 없습니다.
Chrome DevTools에서 네트워크 탭을 열고 결과 리소스 폭포를 살펴보겠습니다.
주 : 최신 크롬에서는 Waterfall 탭을 찾으셔야 합니다.
이번 예제에서는 CRP를 네트워크 탭으로 설명하지만, 엄밀하게는 다른 도구를 사용해야 합니다.
(다운로드랑 실제 html을 해석해서 처리하는건 다른 이야기이기 때문)
https://web.dev/critical-rendering-path-measure-crp/
- html : 약 200ms
- 네트워크 왕복에 100ms, 서버 응답 처리 시간(브라우저의 서버 응답 대기 시간) 100ms가 소요됩니다.(투명한 파란색)
- 즉 절반은 네트워크 연결 대기, 절반은 서버 응답 대기시간입니다.
- 용량이 4k미만으로 매우 작으므로, 한 번의 요청에 처리되며, 응답을 처리하는데 오랜 시간이 걸리지 않습니다. (진한 파란색)
- 네트워크 왕복에 100ms, 서버 응답 처리 시간(브라우저의 서버 응답 대기 시간) 100ms가 소요됩니다.(투명한 파란색)
- jpg: 약 110ms
- 네트워크 왕복 비용 100ms + 서버 응답 처리 시간(브라우저의 서버 응답 대기 시간) 10ms(투명한 보라색)
- 용량이 4k미만으로 매우 작으므로, 한 번의 요청에 처리되며, 응답을 처리하는데 오랜 시간이 걸리지 않습니다. (진한 보라색)
아래 개념들은 해당 링크에서 확인해 주세요
- DomContentLoaded이벤트는: DOM이 모두 준비되었고, JavaScript 실행을 차단하는 스타일시트가 없는 시점을 표시합니다.
- 즉, 이제 (잠재적으로) 렌더 트리를 구성할 수 있음을 의미합니다.
- 로딩 스피너를 표시하는 시점입니다.
- load(or onload)이벤트(보라색 수직선)는 모든 리소스가 다운로드 및 처리 완료되면 발생합니다.
- 즉, 이미지는 DomContentLoaded를 차단하지 않습니다.
- 이미지는 로드 이벤트를 차단합니다.
- 로딩 스피너를 멈추는 시점입니다.
위를 통해 빠른 첫번째 페인트를 제공하기 위해 모든 리소스가 필요한 것은 아님을 알 수 있습니다.
일반적으로 렌더 차단 리소스는 HTML, CSS, JS입니다.
JS와 CSS 추가하기
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="timing.js"></script>
</body>
</html>
JavaScript 및 CSS를 추가한 후 (수직선 이동 살펴보기)
무슨일이 일어난거죠?
- DOM만 필요했던 이전과 달리 CSSOM을 구성하려면 CSS 파일을 가져와 구문 분석해야 합니다
- 렌더 트리를 빌드하려면 DOM과 CSSOM이 모두 필요합니다.
- 이제 구문 분석을 차단하는 자바스크립트 파일이 존재합니다.
- JavaScript를 실행하기 전에 CSS 파일이 다운로드 될 때까지 렌더링을 차단해야 합니다.
- JavaScript가 CSSOM을 쿼리할 수 있기 때문입니다.
- domContentLoaded 이벤트는 CSS 파일이 다운로드 및 구문 분석될 때까지 차단됩니다.
- 즉 html > css > js 순서대로 처리한 뒤 해당 이벤트를 실행합니다.
- JavaScript를 실행하기 전에 CSS 파일이 다운로드 될 때까지 렌더링을 차단해야 합니다.
주 : 구문 분석(parsing)차단되지 않으면 CSSOM 생성 이전에도 렌더링 할 수 있음을 의미합니다.
외부 스크립트를 인라인 스크립트로 바꾸면 어떻게 될까요?
그렇다 하더라도 브라우저는 CSSOM이 구성될 때까지 스크립트를 실행할 수 없습니다.
요컨대 인라인 자바스크립트도 파서를 차단합니다.
CSS 차단이 존재하지만, 스크립트를 인라인하면 페이지가 더 빠르게 렌더링될까요?
Inlined JavaScript:
- 브라우저가 스크립트 태그에 도달하자마자 차단하고 CSSOM이 구성될 때까지 기다리기 때문에 JavaScript가 인라인인지 외부인지는 중요하지 않습니다.
- 또한 첫 번째 예에서 브라우저는 CSS와 JavaScript를 동시에 다운로드하고 거의 동시에 다운로드를 완료합니다.
주 : 해당 스크립트가 CSS를 만질 필요가 없는 경우)
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Async</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script async src="timing.js"></script>
</body>
</html>
domContentLoaded 이벤트는 HTML이 구문 분석된 직후에 발생합니다.
브라우저는 JavaScript에서 차단하지 않는다는 것을 알고 있습니다.
다른 파서 차단 스크립트가 없기 때문에 CSSOM 구성도 병렬로 진행할 수 있습니다.
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Inlined</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
</style>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
- 서로 다른 리소스 간의 종속성 그래프를 이해해야 하고,
- 어떤 리소스가 "cricical"한지 식별해야 하며,
- 페이지에 해당 리소스를 포함하는 방법에 대해 다양한 전략 중에서 선택해야 합니다.
이 문제에 대한 단 하나의 해결책은 없습니다. 페이지마다 다릅니다.
최적의 전략을 파악하려면 유사한 프로세스를 직접 따라야 합니다.
즉, 한발 물러서서 몇 가지 일반적인 성능 패턴을 식별할 수 있는지 봅시다.
성능 패턴
Critical Rendering Path 설명을 위한 어휘 : 중요 경로 특성
- Critical Resource: 페이지의 초기 렌더링을 차단할 수 있는 리소스입니다.
- Critical Path Length: 네트워크 왕복 횟수 혹은 모든 중요 리소스를 가져오는 데 필요한 총 시간입니다.
- Critical Bytes: 모든 중요 리소스 파일 크기의 합계입니다.
- 페이지의 첫 번째 렌더링에 필요한 총 바이트 수입니다.
- 일반적인 렌더링 차단 리소스는 HTML 마크업, CSS 및 JavaScript입니다.
- 이미지는 렌더링 패스를 차단하지 않습니다만, 빨리 그릴수록 좋습니다.
- Javascript와 CSS는 특정 힌트를 통해 렌더링 차단하지 않도록 할 수 있습니다.
가장 간단한 페이지 : HTML 마크업으로만 구성
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
- T0과 T1 사이의 시간은 네트워크 및 서버 처리 시간을 의미합니다.
- 가장 좋은 경우(HTML 파일이 작은 경우) 한 번의 네트워크 왕복으로 전체 문서를 가져옵니다.
- TCP 전송 프로토콜이 작동하는 방식으로 인해 파일이 클수록 더 많은 왕복이 필요할 수 있습니다.
HTML 마크업 + 외부 CSS
Try it<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
- 2 critical resources
- 2 or more roundtrips for the minimum critical path length
- 9 KB of critical bytes
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
- 3 critical resources
- 2 or more roundtrips for the minimum critical path length
- 11 KB of critical bytes
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
(주 : DOM을 만들기 위해선 HTML만 있으면 됨. CSS는 CSSOM을 위해 필요함.)
- 스크립트는 더 이상 파서 차단이 아니며 중요한 렌더링 경로의 일부가 아닙니다.
- 다른 주요 스크립트가 없기 때문에 CSS는 domContentLoaded 이벤트를 차단할 필요가 없습니다.
- domContentLoaded 이벤트가 더 빨리 실행될수록 다른 애플리케이션 로직을 더 빨리 실행할 수 있습니다.
최적화된 페이지는 이제
두 개의 주요 리소스(HTML 및 CSS)가 존재하며,
최소 주요 경로 길이는 두 번 왕복이고,
총 9KB의 주요 바이트를 사용합니다.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" media="print" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
참고 :
https://ui.toast.com/fe-guide/ko_PERFORMANCE
'FrontEnd' 카테고리의 다른 글
리액트 쿼리 : 쿼리 취소하기 (0) | 2022.11.03 |
---|---|
[Remix] 풀 스택 컴포넌트(Full Stack Components) (0) | 2022.11.03 |
리액트와 유닛 테스트를 위한 클린 아키텍처 [부제, 의존성 주입은 정말 필요한가?] (0) | 2022.10.30 |
소프트웨어 엔지니어링 관점에서 바라본 CSS-in-js vs Tailwind CSS (0) | 2022.10.29 |
CSR 성능 최적화를 위한 PRPL 패턴 with React (0) | 2022.10.29 |