블로그
javascript
3遺??뚯슂

파서를 멈추지 마라: script async와 defer의 실행 타이밍

async와 defer는 다운로드를 파싱과 병렬로 돌리지만, 실행 타이밍이 다르다. 순서·DOM 준비 여부를 기준으로 선택하면 로딩 성능과 안정성을 함께 챙길 수 있다.

파서를 멈추지 마라: script async와 defer의 실행 타이밍
🧲
한 줄 결론

대부분의 웹앱에서는 defer를 기본값으로 두고, 완전히 독립적인 스크립트만 async로 돌리는 게 가장 안전합니다.

파서를 멈추지 마라: script async와 defer의 실행 타이밍 (실전 기준)

페이지가 느린 이유가 JS 때문인 건 맞습니다.

하지만 더 정확히 말하면, HTML 파싱(=렌더링 준비)을 막아버리는 JS가 문제예요.

asyncdefer는 둘 다 다운로드를 병렬로 돌려서 속도를 올리지만, 실행 타이밍이 달라서 결과가 완전히 달라집니다.

이 글은 “그래서 내가 뭘 쓰면 되는데?”에 바로 답하는 실전 정리입니다.


결론부터: 언제 뭘 쓰면 되나

  • 순서가 중요하면: defer
    • 예: 라이브러리 → 플러그인 → 내 코드
    • 예: DOM 요소가 있어야 동작하는 코드
  • 완전 독립 스크립트면: async
    • 예: analytics, 광고, A/B 테스트, 히트맵
  • 잘 모르겠으면: defer가 기본으로 안전합니다.

1) 기본 <script>는 왜 위험할까

옵션 없이 <script src="...">를 쓰면, 브라우저는 HTML을 파싱하다가 스크립트를 만나면 다음처럼 행동할 수 있습니다.

  • 스크립트를 다운로드한다.
  • 다운로드가 끝나면 즉시 실행한다.
  • 그동안 HTML 파싱을 멈춘다.

즉, 스크립트 하나가 첫 화면 표시까지의 시간을 통째로 늘릴 수 있어요.


2) async: 다운로드는 병렬, 실행은 끝나는 대로

async는 다운로드를 HTML 파싱과 동시에 합니다.

그런데 문제는 실행 순서예요.

  • 다운로드가 끝난 순간 바로 실행됩니다.
  • 여러 개가 있으면 먼저 끝난 것부터 실행됩니다.

즉, 선언 순서가 보장되지 않습니다.

그래서 다음 조건 중 하나라도 걸리면 async는 “가끔만 깨지는 버그”를 만들기 쉽습니다.

  • 어떤 스크립트가 다른 스크립트에 의존한다.
  • 스크립트가 DOM이 준비되기 전에 실행되면 안 된다.

3) defer: 다운로드는 병렬, 실행은 파싱 끝나고 순서대로

defer도 다운로드는 병렬로 합니다.

하지만 실행은 다릅니다.

  • HTML 파싱이 끝날 때까지 실행을 미룹니다.
  • 그리고 작성된(선언된) 순서대로 실행합니다.

그래서 “순서가 중요한 스크립트 묶음”에 가장 잘 맞아요.


4) 예제로 한 번에 이해하기

아래처럼 라이브러리와 내 코드가 순서대로 실행돼야 한다면, defer가 정답입니다.

html
<!-- ✅ 권장: 의존성이 있는 스크립트 묶음은 defer -->
<script defer src="/js/vendor.js"></script>
<script defer src="/js/app.js"></script>

반대로 “순서가 상관없는 완전 독립 스크립트”라면 async를 고려할 수 있습니다.

html
<!-- ✅ 가능: 독립 스크립트는 async -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>

다만, 독립인지 확신이 없다면 defer로 두는 게 안전합니다.


5) 실전 체크리스트 5문항

아래 5개 중 하나라도 ‘예’면 defer로 가세요.

  • 이 스크립트가 다른 스크립트보다 먼저 실행돼야 한다
  • 이 스크립트가 다른 스크립트가 먼저 실행된 뒤에만 동작한다
  • 이 스크립트가 DOM 요소를 바로 잡아서 이벤트를 붙인다
  • 이 스크립트가 페이지마다 다른 템플릿에서 동작해서, DOM 준비 타이밍이 흔들릴 수 있다
  • “왜 가끔만 깨지지?” 같은 이슈를 한 번이라도 겪었다

6) 자주 하는 실수 3가지 (여기서 많이 무너진다)

  1. 의존성이 있는 스크립트에 async를 섞는다
    • 가장 흔한 ‘간헐적 버그’의 원인입니다.
  2. async 스크립트가 DOM을 전제로 동작한다
    • 어떤 날은 DOM이 아직 없고, 어떤 날은 이미 생겨 있어서 재현이 어렵습니다.
  3. 속도만 보고 실행 순서를 무시한다
    • 성능 최적화의 마지막은 항상 ‘안정성’입니다.

그래서 결론은 (다시 한 번)

  • 대부분은 defer: 순서 보장 + DOM 파싱 이후 실행이라 안정적
  • 정말 독립이면 async: 빨리 끝나면 바로 실행돼 유리할 수 있음

마무리: 지금 당장 적용할 최소 규칙

  • 앱 코드와 라이브러리는 defer로 통일한다.
  • analytics 같은 제3자 스크립트만 async로 분리한다.
  • “가끔만 깨지는 버그”가 있으면, async부터 의심한다.

댓글 유도 CTA

  • 지금 프로젝트에서 async를 쓰는 스크립트가 있다면, 그 스크립트는 정말 독립인가요?
  • 적용해보고 로딩 체감이 어떻게 바뀌었는지, 댓글로 결과를 남겨주세요.

관련 게시물