브라우저에서 JavaScript 로딩 전략 (script, defer, async)
Reference
1. 기본 <script>
의 동작 방식
브라우저는 HTML을 위에서부터 차례로 읽어가며 DOM을 만든다.
그런데 <script>
태그를 만나면 잠시 멈춰서, 자바스크립트를 내려받고 실행한 뒤에야 다시 이어서 파싱을 시작한다. 이 때문에 <script>
는 렌더링 차단 요소(render-blocking resource)
로 분류된다.
<!DOCTYPE html>
<html>
<head>
<script src="main.js"></script>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
- 위 예제에서는
main.js
가 로드될 때까지 브라우저가<body>의 <h1>
을 파싱하지 못하므로, 페이지가 잠시 멈춘 듯 보이는 현상이 발생할 수 있다.
2. defer
와 async
속성의 등장
이 문제를 해결하기 위해 <script>
에는 defer
와 async
속성이 추가되었다.
이 두 속성은 스크립트 다운로드를 HTML 파싱과 병렬로 수행하지만, 실행 시점이 서로 다르다.
속성 | 실행 시점 | HTML 파싱 중단 여부 | 실행 순서 보장 |
---|---|---|---|
<script> | HTML 파싱 중간에 즉시 실행 | 중단됨 | 순서 보장 |
<script defer> | HTML 파싱 완료 후, DOM 완성 시 실행 | 중단 안 됨 | 순서 보장 |
<script async> | 다운로드 완료 시 즉시 실행 (DOM 상태 무관) | 중단 안 됨 | 순서 불확실 |
💡 defer는 DOM 생성이 끝난 뒤 실행되므로 일반적으로 가장 안전한 옵션이다.
3. 렌더링과의 관계
3-1. <script>
가 DOM 생성 과정에 미치는 영향
<script>
가 HTML 중간에 있으면, 브라우저는 DOM 트리를 만들다가 멈춰서 자바스크립트를 실행해야 한다.- 실행 중에 DOM이 완성되지 않았으므로,
document.querySelector()
로 아래쪽 요소를 찾을 때null
이 반환되는 경우가 많다.
이 때문에 보통 스크립트를 <body>
하단에 배치하거나 defer
속성을 사용하는 것이 일반적이다.
<body>
<h1>Hello</h1>
<script src="main.js" defer></script>
</body>
3-2. CSSOM이 늦을 때의 블로킹 현상
브라우저는 DOM + CSSOM이 모두 완성되어야 렌더 트리(Render Tree)
를 만들 수 있다.
따라서 CSS 파일
이 아직 로드되지 않았다면, defer
스크립트도 렌더링을 기다리며 대기한다.
<link rel="stylesheet" href="style.css" />
<script src="main.js" defer></script>
💡 즉, CSS는 직접 스크립트를 막지 않지만, 렌더 트리 생성 지연을 통해 간접적으로 JS 실행을 늦춘다.
4. 자바스크립트 로딩 최적화 전략
4-1. 스크립트 위치 (<head>
vs <body>
하단)
위치 | 특징 |
---|---|
<head> | 초기 스크립트 로딩 빠르지만, 파싱 중단 발생 (렌더링 차단) |
<body> 하단 | HTML 파싱 완료 후 실행되어 안전하지만, 초기 JS 다운로드가 늦을 수 있음 |
💡 최적의 조합: <script src="main.js" defer></script>
→ HTML 파싱과 병렬로 다운로드 + DOM 완성 후 실행
4-2. defer
기본 사용 습관
<script src="app.js" defer></script>
defer
를 기본값처럼 사용하는 게 가장 좋다. 렌더링 차단 없이 예측 가능한 순서를 보장해 준다.
5. 렌더링 차단과 성능 저하의 연결고리
렌더링 차단은 단순히 스크립트 실행이 늦어지는 문제를 넘어, Reflow·Repaint
와 같은 성능 저하 현상으로 이어진다.
<script>
가 DOM 생성을 멈추게 함 → 렌더링 차단 발생- 이후 DOM 구조나 스타일이 자주 변경되면 → Reflow/Repaint 반복
- 결과적으로 화면 깜빡임, 레이아웃 흔들림, 프레임 저하 등 시각적 성능 저하가 나타난다.
즉, 렌더링 차단을 최소화하는 것은 단순한 로딩 최적화가 아니라, 브라우저의 Reflow/Repaint
빈도를 줄이는 첫 단계이기도 하다.
💡 요약
<script>
는 HTML 파싱을 막기 때문에 렌더링 차단 요소다.defer
는 렌더링을 차단하지 않으면서 DOM 완성 후 실행되어 가장 안전하다.async
는 빠르지만, 실행 순서를 제어할 수 없다.- CSS 로딩도 렌더링 차단에 영향을 준다.
- 렌더링 차단 최소화 → 성능 저하 방지 → Reflow/Repaint 최적화로 이어진다.