5가지 JavaScript 우선순위 수준 및 사용 방법
모든 스크립트가 동일하지는 않습니다. 올바른 순서로 로드하세요.

모든 스크립트가 동일하게 생성되지는 않습니다
한 가지는 항상 분명했습니다. 모든 JavaScript가 동일하게 생성되지는 않는다는 것입니다. 일부 스크립트는 '메뉴 상호작용'이나 '장바구니에 추가'와 같은 중요한 상호작용을 처리하는 반면, 다른 스크립트는 중요도가 훨씬 떨어집니다. 사이트를 떠나려는 방문자에게 설문지를 작성하도록 유도하는 '이탈 의도(exit intent)' 팝업 스크립트를 예로 들어보겠습니다. 후자 없이도 살 수 있지만 전자 없이는 웹사이트를 탐색하기가 정말 어려울 것입니다.
하지만 기술적인 측면에서 '일반적인 웹사이트'에서는 이러한 구분이 거의 이루어지지 않습니다. 모든 JavaScript는 페이지에 '그냥 추가'되고 브라우저가 이를 알아서 처리하도록 남겨집니다. 브라우저는 무엇이 중요하고 무엇이 중요하지 않은지 전혀 모르기 때문에 이것은 문제가 됩니다. 개발자인 우리는 알고 있습니다. 그러니 이 문제를 해결해 봅시다!
마지막 검토: Arjen Karel, 2026년 3월
2025 Web Almanac에 따르면 중간 모바일 페이지는 22개의 요청에 걸쳐 646KB의 JavaScript를 로드합니다. 그 JavaScript의 대략 44%는 실행조차 되지 않습니다. 페이지의 13%만이 Lighthouse의 렌더링 차단 리소스 감사를 통과합니다. 문제는 JavaScript를 얼마나 많이 로드하느냐뿐만이 아닙니다. 언제, 어떤 순서로 로드하느냐가 중요합니다.
JavaScript 우선순위가 Core Web Vitals에 미치는 영향
적절한 고려 없이 스크립트를 페이지에 추가하기만 하면 3가지 Core Web Vitals 모두에 영향을 미칠 수 있습니다. Largest Contentful Paint, Interaction to Next Paint 및 Cumulative Layout Shift입니다.

예시: 렌더링을 차단하는 JavaScript로 인해 LCP 네트워크 리소스가 지연됩니다.
Largest Contentful Paint는 대역폭 및 CPU 경쟁에 취약합니다. 너무 많은 스크립트가 초기 네트워크 리소스를 두고 경쟁하면 Largest Contentful Paint 네트워크 리소스가 지연되고 초기 CPU 작업은 메인 스레드를 차단하여 LCP를 지연시킵니다.
Interaction to Next Paint는 상호작용 직전에 실행되는 스크립트의 영향을 받을 수 있습니다. 스크립트가 실행되면 메인 스레드를 차단하고 실행 시간 동안 상호작용을 지연시킵니다.
스크립트가 '페이지가 보이는 방식'을 변경하는 경우 Cumulative Layout Shift를 일으킬 수도 있습니다. 페이지에 배너와 슬라이더를 주입하는 광고 스크립트가 이를 유발하는 것으로 악명이 높습니다.
5가지 JavaScript 우선순위 유형
저는 JavaScript 우선순위를 5가지 유형으로 구분하는 것을 좋아합니다.
- Render Critical: 이 스크립트는 최악의 스크립트 중 하나입니다. 페이지 레이아웃을 변경하며 이 스크립트를 로드하지 않으면 레이아웃이 완전히 달라집니다. 예: 일부 슬라이더 스크립트 또는 A/B 테스트.
- Critical Scripts: 이 스크립트는 중요한 페이지 기능을 처리하며 이 스크립트 없이는 제품 장바구니 추가, 사이트 검색 또는 탐색과 같은 중요한 작업이 불가능합니다.
- Important Scripts: 이 스크립트는 중요한 비즈니스 로직을 처리하며 사이트는 이에 의존합니다. 예: 애널리틱스.
- Nice to Have Scripts: 이 스크립트는 있으면 좋지만 꼭 필요한 것은 아니며 페이지가 작동하는 데 반드시 필요한 것은 아닙니다. 예를 들어 채팅 위젯이나 이탈 의도 팝업이 있습니다.
- Future Scripts: 이 스크립트는 중요하거나 있으면 좋을 수 있지만, 실제로 스크립트를 사용하기 전에 '다른 단계'를 거쳐야 하므로 당장 필요하지는 않습니다. 예를 들어 다단계 체크아웃 스크립트가 있습니다.
각 우선순위 수준을 살펴보기 전에 Chrome이 다양한 스크립트 유형에 네트워크 우선순위를 할당하는 방법은 다음과 같습니다:
| Script type | Chrome priority | Blocks rendering? |
|---|---|---|
<script> in <head> | Highest | Yes |
<script async> | Low (download) / High (execute) | No |
<script defer> | Low | No |
<script> at end of <body> | Medium / High | No |
이러한 기본값을 이해하는 것이 리소스 우선순위 지정(resource prioritization)의 핵심입니다.
1. Render-Critical Scripts
이 스크립트는 페이지의 레이아웃을 직접 변경합니다. 이 스크립트가 없으면 페이지가 의도한 디자인과 완전히 다르게 보입니다. 예로는 로딩 프로세스 초기에 레이아웃을 변경하는 슬라이더 또는 A/B 테스트 프레임워크용 스크립트가 있습니다.
이러한 스크립트의 문제는 연기(defer)하거나 지연시킬 수 없다는 것입니다. 지연이 발생하면 웹사이트 레이아웃이 이동하여 UX가 저하되고 Core Web Vitals가 실패하게 됩니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<link href="styles.css" rel="stylesheet" />
<script src="render-critical.js"></script>
</head>
<body></body>
</html>
Best Practices:
- 가능한 한 render-critical 스크립트를 피하세요. 이러한 유형의 스크립트에 대한 의존성을 제거하도록 코드를 다시 작성하세요.
- 피할 수 없는 경우 이러한 스크립트의 절대적으로 필요한 부분만 인라인으로 삽입하거나 로드하세요.
- 이러한 스크립트를 defer 또는 async로 설정하지 말고 head 맨 위에 배치하여 '가능한 한 빨리' 다운로드되도록 트리거하세요.
2. Critical Scripts
이 스크립트는 근본적인 상호작용을 가능하게 합니다. 이 스크립트 없이는 사이트 탐색, 장바구니에 항목 추가, 쿠키 알림 또는 검색 수행과 같은 중요한 작업이 불가능해집니다. 이들은 사이트의 핵심 기능에 필수적입니다.
이러한 스크립트는 async 또는 defer 속성과 함께 페이지의 head에 배치해야 합니다. 이 두 가지 방식에 대한 전체 비교는 async 대 defer 및 이것이 Core Web Vitals에 미치는 영향을 참조하세요.
<script defer src="critical.js"></script> <script async src="critical.js"></script>
Best Practices:
- 이와 같은 스크립트를 최소한으로 유지하고 이 기능을 덜 중요한 다른 기능과 결합하지 마세요.
- 종속성에 따라
async또는defer를 사용하여 이러한 스크립트를 일찍 로드하세요. - Real User Monitoring을 사용하여 실행 병목 현상을 식별하고 성능이 사용자의 요구 사항과 일치하는지 확인하세요.
3. Important Scripts
이러한 스크립트는 비즈니스를 지원하지만 페이지가 작동하는 데 필요하지는 않습니다. 예를 들어 애널리틱스 스크립트는 필수 데이터를 제공하지만 더 중요한 시각적 요소보다 먼저 로드할 필요는 없습니다. critical 스크립트와 important 스크립트의 구분은 논쟁의 여지가 있을 수 있으므로 이 우선순위를 설정하기 전에 모든 이해관계자와 논의하세요!
이러한 유형의 스크립트에 대한 스크립트 우선순위를 낮추는 신뢰할 수 있는 방법은 2가지가 있습니다.
<html>
<head>
<!-- method 1: inject after DOMContentLoaded -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var script = document.createElement('script');
script.src = 'important.js';
document.body.appendChild(script);
});
</script>
</head>
<body>
<!-- method 2: place at the bottom of the page -->
<script defer src="important.js"></script>
</body>
</html>
1: DOMContentLoaded 이후 주입
DOMContentLoaded 이벤트 이후에 스크립트를 주입하면 HTML이 완전히 구문 분석된 직후에 스크립트 다운로드가 시작되도록 보장할 수 있습니다. 이를 통해 이미지 및 글꼴과 같은 발견 가능한(discoverable) 리소스가 우선순위를 갖게 됩니다. 이 방법은 균형을 제공합니다. 스크립트가 기능 지연을 피할 수 있을 만큼 일찍 로드되기 시작하지만 초기 페이지 렌더링에 중요한 초기 리소스와 경쟁하지 않습니다.
2: 페이지 하단에 배치
이 고전적인 기술은 브라우저가 전체 문서를 처리할 때까지 스크립트 로딩을 연기하며 기술 1과 거의 같은 결과를 얻습니다. 유일한 차이점은 기술 1은 브라우저의 프리로드 스캐너를 건너뛰는 반면 이 기술은 그렇지 않다는 것입니다. 프리로드 스캐너는 브라우저가 중요한 리소스를 빠르게 식별하고 대기열에 넣는 데 사용하는 가벼운 빠른 스캐너입니다. 뷰포트 내에 지연 로드(lazy loaded) 이미지가 있을 가능성이 있는 경우 프리로드 스캐너를 건너뛰는 것이 좋을 수 있으며, 프리로드 스캐너를 사용하면 이 스크립트의 로딩 속도가 빨라집니다.
fetchpriority에 대한 참고 사항: 연기된(deferred) 스크립트에 fetchpriority="low"를 사용하면 우선순위가 낮아질 것이라고 기대할 수 있습니다. 그렇지 않습니다. defer 및 async 스크립트는 Chrome에서 이미 Low 우선순위로 로드됩니다. 여기에 fetchpriority="low"를 추가하는 것은 아무런 효과가 없습니다(no-op). fetchpriority 속성은 우선순위를 Highest에서 High로 떨어뜨릴 수 있는 차단(blocking) 스크립트에서만 차이를 만듭니다. JavaScript를 연기하는 더 많은 방법은 전체 가이드를 참조하세요.
4. Nice-to-Have Scripts
이 스크립트는 UX를 향상시키지만 사이트가 작동하는 데 필요하지는 않습니다. 예로는 채팅 위젯, 고객 피드백 팝업 또는 선택적 애니메이션이 있습니다.
이러한 스크립트는 'lazy on load'라는 패턴에 이상적인 후보입니다. 즉, 페이지의 로드 이벤트를 기다린 다음 유휴 시간(idle time) 동안 스크립트를 주입하는 것을 의미합니다. 로드 이벤트를 기다리면 스크립트가 더 중요한 초기 리소스와 대역폭 및 CPU를 놓고 경쟁하지 않도록 보장합니다. 유휴 순간을 기다리면 브라우저가 사용자 입력과 같은 더 중요한 작업을 처리하지 않도록 보장합니다.
작동하는 예시는 다음과 같습니다:
window.addEventListener("load", () => {
const idle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
idle(() => {
const script = document.createElement("script");
script.src = "/path/to/script.js";
document.head.appendChild(script);
});
});
Safari는 requestIdleCallback을 지원하지 않으므로 setTimeout 폴백(fallback)이 필요합니다. 실행을 더 작은 청크로 분할해야 하는 스크립트의 경우 scheduler.yield()를 사용하여 메인 스레드의 응답성을 유지하고 INP 점수를 보호하는 것을 고려하세요.
Best Practices:
- 페이지가 로드된 후 이러한 스크립트를 지연 로드(lazy-load)하고 유휴 순간을 기다립니다.
- 이 패턴으로 로드된 스크립트가 빠르게 로드된다고 보장할 수 없음을 이해하세요.
CoreDash에서 모니터링하는 사이트 전반에 걸쳐 중요하지 않은 스크립트를 로드 이벤트 이후로 연기하는 페이지는 INP에서 84%가 'good' 점수를 받은 반면, 모든 스크립트를 즉시 로드하는 페이지는 61%였습니다.
5. Future Scripts
Future 스크립트는 특정 조건이 충족될 때까지 필요하지 않은 스크립트입니다. 예를 들어 다단계 체크아웃 스크립트는 사용자가 장바구니에 항목을 추가한 후에만 관련이 있습니다. 이러한 스크립트는 사용자 여정의 훨씬 나중까지 기다릴 수 있습니다.
이 예시를 살펴보세요. IntersectionObserver를 사용하여 폼(form)이 표시되는 뷰포트에 있을 때와 같이 필요할 때만 JS 로직을 로드합니다.
<!DOCTYPE html>
<html>
<head>
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.querySelector("form");
const observer = new IntersectionObserver(function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const script = document.createElement("script");
script.src = "/sign-up.js";
document.head.appendChild(script);
observer.unobserve(form);
}
});
});
observer.observe(form);
});
</script>
</head>
<body>
<form action="/sign-up" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
<button type="submit">Sign Up</button>
</form>
</body>
</html>
Best Practices:
- 사용자 작업에 의해 트리거되어 요청 시(on demand) 이러한 스크립트를 로드하세요.
- 코드 분할(code-splitting) 기술을 사용하여 각 단계에서 필요한 부분만 전송하세요.
- 사용자가 특정 섹션으로 스크롤할 때와 같이 필요할 때만 동적으로 주입하세요.
대부분의 사이트에는 두 가지 패턴만 필요합니다. 기능적인 것에는 defer를, 그 외 모든 것에는 load+idle 패턴을 사용합니다. 스크립트의 fetchpriority를 미세 조정하는 데 시간을 보내고 있다면, 아마도 상황을 너무 복잡하게 만들고 있는 것일 수 있습니다. 큰 이득을 먼저 얻으세요. 연기할 수 있는 것은 연기하고, 지연시킬 수 있는 것은 지연시키고, 필요 없는 것은 삭제하세요.

