Skip to content

브라우저에서 JavaScript 로딩 전략 (script, defer, async)


1. 기본 <script>의 동작 방식

브라우저는 HTML을 위에서부터 차례로 읽어가며 DOM을 만든다.
그런데 <script> 태그를 만나면 잠시 멈춰서, 자바스크립트를 내려받고 실행한 뒤에야 다시 이어서 파싱을 시작한다. 이 때문에 <script>렌더링 차단 요소(render-blocking resource)로 분류된다.

html
<!DOCTYPE html>
<html>
  <head>
    <script src="main.js"></script>
  </head>
  <body>
    <h1>Hello</h1>
  </body>
</html>
  • 위 예제에서는 main.js가 로드될 때까지 브라우저가 <body>의 <h1>을 파싱하지 못하므로, 페이지가 잠시 멈춘 듯 보이는 현상이 발생할 수 있다.

2. deferasync 속성의 등장

이 문제를 해결하기 위해 <script>에는 deferasync 속성이 추가되었다.
이 두 속성은 스크립트 다운로드를 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 속성을 사용하는 것이 일반적이다.

html
<body>
  <h1>Hello</h1>
  <script src="main.js" defer></script>
</body>

3-2. CSSOM이 늦을 때의 블로킹 현상

브라우저는 DOM + CSSOM이 모두 완성되어야 렌더 트리(Render Tree)를 만들 수 있다.
따라서 CSS 파일이 아직 로드되지 않았다면, defer 스크립트도 렌더링을 기다리며 대기한다.

html
<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 기본 사용 습관

js
<script src="app.js" defer></script>

defer를 기본값처럼 사용하는 게 가장 좋다. 렌더링 차단 없이 예측 가능한 순서를 보장해 준다.


5. 렌더링 차단과 성능 저하의 연결고리

렌더링 차단은 단순히 스크립트 실행이 늦어지는 문제를 넘어, Reflow·Repaint와 같은 성능 저하 현상으로 이어진다.

  1. <script>가 DOM 생성을 멈추게 함 → 렌더링 차단 발생
  2. 이후 DOM 구조나 스타일이 자주 변경되면 → Reflow/Repaint 반복
  3. 결과적으로 화면 깜빡임, 레이아웃 흔들림, 프레임 저하 등 시각적 성능 저하가 나타난다.

즉, 렌더링 차단을 최소화하는 것은 단순한 로딩 최적화가 아니라, 브라우저의 Reflow/Repaint 빈도를 줄이는 첫 단계이기도 하다.


💡 요약

  • <script>는 HTML 파싱을 막기 때문에 렌더링 차단 요소다.
  • defer는 렌더링을 차단하지 않으면서 DOM 완성 후 실행되어 가장 안전하다.
  • async는 빠르지만, 실행 순서를 제어할 수 없다.
  • CSS 로딩도 렌더링 차단에 영향을 준다.
  • 렌더링 차단 최소화 → 성능 저하 방지 → Reflow/Repaint 최적화로 이어진다.