파서를 멈추지 마라: script async와 defer의 실행 타이밍
async와 defer는 다운로드를 파싱과 병렬로 돌리지만, 실행 타이밍이 다르다. 순서·DOM 준비 여부를 기준으로 선택하면 로딩 성능과 안정성을 함께 챙길 수 있다.
대부분의 웹앱에서는 defer를 기본값으로 두고, 완전히 독립적인 스크립트만 async로 돌리는 게 가장 안전합니다.
파서를 멈추지 마라: script async와 defer의 실행 타이밍 (실전 기준)
페이지가 느린 이유가 JS 때문인 건 맞습니다.
하지만 더 정확히 말하면, HTML 파싱(=렌더링 준비)을 막아버리는 JS가 문제예요.
async와 defer는 둘 다 다운로드를 병렬로 돌려서 속도를 올리지만, 실행 타이밍이 달라서 결과가 완전히 달라집니다.
이 글은 “그래서 내가 뭘 쓰면 되는데?”에 바로 답하는 실전 정리입니다.
결론부터: 언제 뭘 쓰면 되나
- 순서가 중요하면:
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가 정답입니다.
<!-- ✅ 권장: 의존성이 있는 스크립트 묶음은 defer -->
<script defer src="/js/vendor.js"></script>
<script defer src="/js/app.js"></script>반대로 “순서가 상관없는 완전 독립 스크립트”라면 async를 고려할 수 있습니다.
<!-- ✅ 가능: 독립 스크립트는 async -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>다만, 독립인지 확신이 없다면 defer로 두는 게 안전합니다.
5) 실전 체크리스트 5문항
아래 5개 중 하나라도 ‘예’면 defer로 가세요.
- 이 스크립트가 다른 스크립트보다 먼저 실행돼야 한다
- 이 스크립트가 다른 스크립트가 먼저 실행된 뒤에만 동작한다
- 이 스크립트가 DOM 요소를 바로 잡아서 이벤트를 붙인다
- 이 스크립트가 페이지마다 다른 템플릿에서 동작해서, DOM 준비 타이밍이 흔들릴 수 있다
- “왜 가끔만 깨지지?” 같은 이슈를 한 번이라도 겪었다
6) 자주 하는 실수 3가지 (여기서 많이 무너진다)
- 의존성이 있는 스크립트에 async를 섞는다
- 가장 흔한 ‘간헐적 버그’의 원인입니다.
- async 스크립트가 DOM을 전제로 동작한다
- 어떤 날은 DOM이 아직 없고, 어떤 날은 이미 생겨 있어서 재현이 어렵습니다.
- 속도만 보고 실행 순서를 무시한다
- 성능 최적화의 마지막은 항상 ‘안정성’입니다.
그래서 결론은 (다시 한 번)
- 대부분은
defer: 순서 보장 + DOM 파싱 이후 실행이라 안정적 - 정말 독립이면
async: 빨리 끝나면 바로 실행돼 유리할 수 있음
마무리: 지금 당장 적용할 최소 규칙
- 앱 코드와 라이브러리는
defer로 통일한다. - analytics 같은 제3자 스크립트만
async로 분리한다. - “가끔만 깨지는 버그”가 있으면,
async부터 의심한다.
댓글 유도 CTA
- 지금 프로젝트에서
async를 쓰는 스크립트가 있다면, 그 스크립트는 정말 독립인가요? - 적용해보고 로딩 체감이 어떻게 바뀌었는지, 댓글로 결과를 남겨주세요.