스크립트 때문에 페이지가 느려진다고요?
웹 페이지를 만들다 보면 JavaScript 파일이 점점 많아지죠. 그런데 이 스크립트들이 페이지 로딩을 방해하고 있다는 사실, 알고 계셨나요?
문제는 이런 거예요. 브라우저가 HTML을 읽다가 <script>
태그를 만나면 “잠깐, 이거 먼저 다운로드하고 실행해야겠다!” 하면서 HTML 파싱을 멈춰버려요. 스크립트가 크면 클수록 사용자는 더 오래 빈 화면을 봐야 하죠.
하지만 걱정 마세요! 몇 가지 간단한 속성만 추가해도 페이지 로딩 속도를 크게 개선할 수 있어요. 실제로 우리 프로젝트에서도 이런 최적화로 체감할 수 있을 정도로 빨라졌거든요!
어떤 방법들이 있는지 하나씩 알아볼까요?
defer: “나중에 실행할게요!”
defer
속성은 스크립트를 백그라운드에서 조용히 다운로드하게 해줘요. HTML 파싱은 멈추지 않고 계속 진행되고, DOM이 완성된 후에 스크립트를 실행하죠.
<p>이 콘텐츠는 바로 표시돼요!</p>
<script>
document.addEventListener('DOMContentLoaded', () =>
alert("DOM이 준비되고 defer 스크립트도 실행됐어요!")
);
</script>
<script defer src="long.js"></script> {}
<p>이 콘텐츠도 바로 표시돼요!</p>
실행 순서가 중요해요
defer 스크립트들은 HTML에 등장한 순서대로 실행돼요. 이게 핵심이에요!
<script defer src="long.js"></script> <!-- 첫 번째로 실행 -->
<script defer src="small.js"></script> <!-- 두 번째로 실행 -->
재미있는 건, 두 스크립트가 병렬로 다운로드되지만 실행은 순서대로 된다는 거예요. small.js
가 먼저 다운로드 완료되더라도 long.js
를 기다린 후에 실행됩니다.
언제 사용하면 좋을까요? DOM이 완성된 후에 실행되어야 하는 스크립트들, 특히 DOM 요소에 이벤트 리스너를 추가하거나 초기화 작업을 하는 스크립트에 최적이에요!
async: “내 멋대로 할래요!”
async
속성은 정말 독립적이에요. 다운로드되는 즉시 실행되고, DOM이 준비됐는지도 신경 안 써요. 자유분방합니다.
<p>이 콘텐츠는 바로 표시돼요!</p>
<script>
document.addEventListener('DOMContentLoaded', () =>
alert("DOM이 준비됐어요!")
);
</script>
<script async src="long.js"></script> {}
<script async src="small.js"></script> {}
<p>이 콘텐츠도 바로 표시돼요!</p>
async의 예측불가한 매력
async 스크립트들은 먼저 다운로드되는 놈이 먼저 실행돼요. 순서 따윈 없어요!
small.js
가 빨리 다운로드되면? → 먼저 실행!long.js
가 먼저 완료되면? → 그게 먼저 실행!DOMContentLoaded
이벤트? → 서로 기다리지 않아요!
<script async src="analytics.js"></script> <!-- 언제 실행될지 몰라요 -->
<script async src="ads.js"></script> <!-- 이것도 언제 실행될지 몰라요 -->
주의하세요! 스크립트끼리 의존성이 있다면 async를 쓰면 안 돼요. A 스크립트가 B 스크립트보다 먼저 실행되어야 한다면 defer를 사용하세요!
언제 사용하면 좋을까요? Google Analytics, 광고 스크립트처럼 다른 코드에 의존하지 않는 독립적인 스크립트에 완벽해요!
작은 스크립트는 그냥 HTML에 넣어버리기
가끔 정말 작은 스크립트 파일이 있잖아요? 몇 줄 안 되는 유틸리티 함수 같은 거 말이에요. 이런 경우엔 외부 파일로 분리하지 말고 HTML에 직접 넣는 게 더 빠를 수 있어요!
왜냐하면 외부 스크립트를 불러오려면 네트워크 요청이 필요하거든요. 작은 파일을 위해 굳이 네트워크 왕복을 할 필요가 있을까요?
Before: 외부 스크립트
작은 파일도 네트워크 요청이 필요해요
언제 사용하면 좋을까요?
인라인 스크립트가 좋은 경우:
- 스크립트 크기가 1-2KB 이하로 작을 때
- 한 페이지에서만 사용하는 스크립트일 때
- 초기 로딩 속도가 중요한 랜딩 페이지
외부 스크립트가 좋은 경우:
- 여러 페이지에서 재사용하는 스크립트
- 크기가 큰 스크립트 (브라우저 캐싱 활용)
필요할 때만 불러오는 동적 로딩
가장 강력한 최적화 방법이에요! 사용자가 실제로 필요로 할 때만 스크립트를 불러오는 거죠.
예를 들어볼게요. 모달 창을 여는 스크립트가 있다고 해봐요. 사용자가 모달을 열지도 않을 건데 처음부터 불러올 필요가 있을까요? 필요할 때 불러오면 되잖아요!
const loadScript = (src) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = false; // 순서 보장하고 싶다면
script.onload = resolve;
script.onerror = reject;
document.body.append(script);
});
};
// 모달 버튼 클릭 시에만 로딩
document.getElementById('modal-btn').addEventListener('click', async () => {
await loadScript('modal.js');
await loadScript('modal-animations.js');
// 이제 모달을 열 수 있어요!
openModal();
});
더 간단한 방법: ES6 dynamic import
document.getElementById('modal-btn').addEventListener('click', async () => {
const { openModal } = await import('./modal.js');
openModal();
});
실제 사용 사례:
- 사용자가 특정 기능을 사용할 때만 로딩
- A/B 테스트로 일부 사용자에게만 스크립트 제공
- 모바일에서는 무거운 스크립트 생략
- 다크 모드 전환 시에만 관련 스크립트 로딩
실제 적용 사례: 스마트한 조건부 로딩
이론만 봐서는 뭔가 아쉽죠? 실제로 이런 최적화를 어떻게 활용하는지 보여드릴게요!
우리 프로젝트에서는 사용자가 “애니메이션 줄이기” 설정을 활성화했을 때만 관련 스크립트를 불러와요. 모든 사용자에게 필요하지 않은 기능이니까, 필요한 사람에게만 제공하는 거죠!
1단계: 기본 스크립트 로딩
모든 사용자에게 동일한 스크립트를 로딩
이제 정말 필요한 사용자에게만 스크립트를 제공할 수 있게 되었어요! 리소스도 절약하고 성능도 개선하는 일석이조의 효과를 얻었죠!
아래 gif에서 “애니메이션 줄이기”를 활성화하면 필요한 스크립트를 동적으로 로딩하고, 버튼 색상 전환이나 테마 버튼 회전 애니메이션이 비활성화되는 걸 확인할 수 있어요:
어떤 방법을 언제 사용할까요?
헷갈리시죠? 정리해드릴게요! 각 방법이 언제 가장 빛나는지 알아봐요:
방법 | 이럴 때 사용하세요! | 장점 | 주의사항 |
---|---|---|---|
defer | DOM 조작하는 스크립트 | ✅ 순서 보장 ✅ HTML 파싱 안 멈춤 | DOM 완성 후에만 실행 |
async | 독립적인 스크립트 (Analytics, 광고) | ✅ 완전 독립적 ✅ 최대한 빠른 실행 | ❌ 순서 보장 안됨 |
인라인 | 1-2KB 이하 작은 스크립트 | ✅ 네트워크 요청 제거 ✅ 즉시 실행 | ❌ 캐싱 불가 ❌ 재사용 어려움 |
동적 로딩 | 조건부로 필요한 기능 | ✅ 필요할 때만 로딩 ✅ 메모리 절약 | 구현이 조금 복잡 |
실무 꿀팁: 처음엔 defer
부터 시작해보세요! 대부분의 상황에서 가장 안전하고 효과적이에요. 그다음에 필요에 따라 다른 방법들을 적용해보세요!
마무리하며
스크립트 로딩 최적화, 어떠셨나요? 처음엔 복잡해 보이지만 하나씩 적용해보면 생각보다 간단해요!
- ✅ defer: 순서 보장하면서 백그라운드 로딩
- ✅ async: 독립적인 스크립트를 위한 자유분방한 로딩
- ✅ 인라인: 작은 스크립트는 아예 HTML에 포함
- ✅ 동적 로딩: 필요할 때만 불러오는 스마트한 방법
다음 단계는? 이런 최적화는 작은 파일에서는 큰 차이가 안 날 수도 있어요. 하지만 사이드 프로젝트야말로 이런 실험을 부담 없이 해볼 수 있는 최고의 환경이죠! 한 번씩 적용해보면서 체감해보세요!
브라우저 호환성도 걱정 없어요
- defer, async: 모든 모던 브라우저에서 지원 (IE9+까지도!)
- 동적 import: ES2020 기능이라 최신 브라우저에서만 동작 (폴리필로 해결 가능)
- 인라인 스크립트: 모든 브라우저에서 문제없이 동작