Render Blocking 요소에 대해서 알아 볼겁니다.
Render Blocking이란 Render를 멈추게 하는 것이고 Render가 멈추면 초기 화면이 늦게 뜨기 때문에 사용자에게 더 좋은 경험을 주려면 Render Blocking 요소를 최소화 하는 것이 좋습니다.
Render Blocking이 일어나는 세가지 경우는 아래와 같습니다.
1. HTML, CSS로 인한 Render Blocking
2. Script Tag의 DOM Parser Blocking에 인한 Render Blocking
3. CSSOM의 script execute Blocking에 의한 Render Blocking
저번 게시글에서 우리는 Rendering Tree를 만들기 위해서 DOM과 CSSOM이 필요 하다는 것을 알았습니다.
그렇기 때문에 HTML과 CSS둘 다 Render Blocking 요소에 해당합니다.
DOM이 없다면 렌더링 할 요소가 없기때문에 명확하지만 CSS는 바로 이해가 가지 않을 수 있습니다.
위의 비교사진을 보면 오른쪽의 CSS가 적용된 페이지보다 왼쪽의 CSS없이 랜더링 된 페이지의 가독성이 현저히 떨어집니다.
그렇기 때문에
브라우저에서는 DOM과 CSSOM을 모두 사용 할 수 있게 될 때 까지 렌더링을 차단합니다.
CSS는 렌더차단 요소입니다. 최초 렌더링에 걸리는 시간을 최적화하려면 클라이언트에 최대한 빠르게 다운로드되어야 합니다.
<link href="style.css" rel="stylesheet"> // 1KB
<link href="pc.css" rel="stylesheet"> // 100KB
<link href="tablet.css" rel="stylesheet"> // 100KB
<link href="mobile.css" rel="stylesheet"> // 100KB
예를 들어 데스크탑, 모바일, 테블릿 환경을 모두 고려한 CSS파일이 있다고 하고 각 관경당 CSS가 100KB의 크기를 가진다고 할 때 CSSOM은 300KB의 CSS파일을 다운로드 해야 합니다. 300KB의 CSS파일을 다운로드하는동안 렌더링은 차단될것 입니다.
하지만 문제를 미디어쿼리를 사용하여 상황에 따라 불필요한 부분을 non-blocking resource로 만들어 해결 할 수있습니다.
<link href="style.css" rel="stylesheet">
<link href="pc.css" rel="stylesheet" media="screen and (min-width: 64em)">
<link href="tablet.css" rel="stylesheet" media="screen and (min-width: 48em) and (max-width:63.938em)">
<link href="mobile.css" rel="stylesheet" media="screen and (max-width: 47.938em)">
( 미디어 쿼리에 대한 설명 MDN 미디어쿼리 )
위의 미디어 쿼리는 스크린의 사이즈가(Base picel 16px 기준)
64em(1024px)이상 일때는 pc.css를 사용하고
48em(768px)이상 63.xxem(1023px)이하 일때는 tablet.css를 사용하고
47.xxx(767px)이하 일때는 mobile.css를 사용합니다.
(style.css는 공통적으로 사용하는 CSS 입니다. media="all" 과 같습니다.)
그럼 PC환경에서 처음 로드될 때는 style.css와 pc.css만 렌더차단요소로 처리되어
style.css와 pc.css가 다운로드되고 파싱되는동안 Rendering이 차단되고
tablet.css와 mobile.css는 비차단 요소로 처리되어 렌더링에 영향을 주지 않습니다.
이렇게하면 100KB를 다운로드 하는 동안만 렌더링이 차단됩니다!
렌더차단 여부는 브라우저가 해당 요소에 대해 페이지의 초기 렌더링을 보류해야 하는지 여부만 나타냅니다. 비차단 요소의 우선순위는 낮지만 브라우저는 CSS 요소를 다운로드합니다.
그러므로 위 예제에서 tablet.css와 mobile.css는 현재 필요한 CSS는 아니지만 다운로드 합니다.
미디어쿼리를 사용 하는것이 저는 단지 반응형 스타일을 만들때 사용 하는 것 인 줄 알았는데 이런 장점도 있다는것을 처음 알았네요
자바스크립트는 브라우저에서 실행되고 페이지 동작 방식에 대한 거의 모든 측면을 변경할 수 있게 하는 동적 언어입니다. DOM 트리에서 요소를 추가하고 제거하여 콘텐츠를 수정하거나, 각 요소의 CSSOM 속성을 수정하거나, 사용자 입력을 처리하는 등의 많은 작업을 수행할 수 있습니다.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>첫번째 span!</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
// 아직 Build 되지 않은 DOM요소에 접근하면 Error 발생
var span = document.getElementsByTagName('span')[1]; // Error
</script>
<p>Hello <span>두번째 span!</span> students!</p>
</body>
</html>
위의 예제를 보게되면 script Tag에서 span[0]에 접근 할때는 에러가 발생하지 않지만
맨 아랫줄 span[1]에 접근 하면 에러가 발생됩니다.
그 이유는 DOM파싱을 진행하다 Script Tag를 만나게 되면 DOM파싱을 중지하고 자바스크립트 엔진에 제어권한을 넘깁니다. 그리고 스크립트를 모두 실행 하고 나면 다시 DOM을 그리기 시작합니다.
그렇기 때문에 Javascript가 실행될때 아직 span[1]은 파싱되지 않았기 때문에 DOM에 존재 하지 않고 접근 할수 없습니다.
여기서 알수 있는 것은 인라인 스크립트를 실행하게 되면 DOM parser Blocking하게 되고 결과적으로 Rendering Block도 같이 일어나게 되고 초기렌더링이 지연된다는 것 입니다.
자바스크립트는 DOM에 새로운 요소를 추가 할 수있고 이 요소의 스타일을 지정 할 수 있습니다. 그런데 스크립트 실행을 하려는데 CSS의 다운로드와 빌드작업이 완료되지 않았을때는 브라우저가 CSSOM생성 완료시점 까지 Javascript의 실행을 지연시킵니다. (Chrome은 unload된 css에 접근 하는 때 만 실행을 중지)
( CSS는 Javascript의 실행도 Block 하고 Rendering Block 하고 정말 안걸리는 곳이 없는 불편한 놈이네요.. 그만큼 중요하게 생각하는 부분이군요 )
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path: Script Async</title>
</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>
script Tag에 async를 적어주면 브라우저에게 다운받는동안 DOM생성을 중단하지 말라고 지시 하는것이며 Parser가 script를 다운받는 동안 DOM을 파싱하고 script 다운로드가 완료되면 스크립트가 실행되면서 Parser가 Block 됩니다. 이 경우 성능은 크게 향상됩니다.
흐름은 위의 도식과 같습니다.
하지만 async는 두가지 단점이 존재 하는데
하나는 스크립트 실행시 DOM Parser를 Block한다는것
하나는 로드된 순서로 실행하므로 여러개의 스크립트를 필요로 하고 그 스크립트들이 서로 의존성을 가질때 실행시점에서 문제가 발생 할 수 있습니다.
(예시 Javascript async error)
이 문제를 defer로 해결 할 수 있습니다. defer를 적용 하게 되면 script download는 async와 같이 비동기로 진행을 하게 되고 DOM이 모두 완성 된 후에 스크립트 태그의 순서로 실행됩니다. 그렇기 때문에 async의 단점을 해결하게 됩니다.
defer의 단점은 TTI(Time To Interactive)가 상대적으로 길다는 것 입니다. (SSR의 hydrate처럼 화면이 완성 됐어도 기능이 작동 하지 않을 수 잇다는 의미)
<script>
<script async>
<script defer>
각 옵션별 차이를 잘 나타 내주는 표 (출처)
경우1. DOM트리 완료 / CSSOM트리가 파싱이 안되면 렌더링이 안됨
→ Render blockig
경우 2. CSSOM 미완료 / Javascript 실행 상태
→ 스크립트 실행이 CSSOM에 블록되므로 script blocking
경우 3. CSSOM 완료 / Javascript 다운로드 or 실행 / DOM트리 파싱중지 상태라면?
→ DOM Parser를 block 했으므로 Parser Blocking!
→ 현 시점까지 파싱된 DOM을 가지고 CSSOM으로 Render Tree 만들어서 FP 가능
→ But, 자바스크립트 작업이 끝날때 까지 Render Tree에 있는 것을 다 Paint했다면 더이상 Render가 안됨
→ 이 상황에서는 Render Blocking
그럼 여기서 하나 더 궁금해졌는데 inline script를 사용 할 때도 defer를 사용 하면 Parser를 Blocking 하지 않는가?
그래서 검색 해봤더니 스텍오버플로우에 이런 글 이 있었다.
script에 src 속성이 없으면 defer가 효과가 없다고 한다. inline script를 defer처럼 DOM 작업이 완료된 후 script를 실행 하고 싶다면 'DOMContentsLoaded'라는 이벤트에 EventListner를 추가 하여 사용 하면 된다고 한다
https://www.debugbear.com/blog/avoid-css-import
이 글을 보면서 vs @import 관련된 글을 포스팅 하려던 중 Render Blocking 이라는 것이 나오길래 궁금했는데 쉽게 이해할 수 있도록 글을 작성해주셔서 감사합니다.