브라우저는 자체적으로 리소스 요청의 우선순위를 정하는 데 매우 능숙합니다. 하지만 항상 잘 해내는 것은 아닙니다. 우선순위 힌트를 사용하면 어떤 네트워크 활동이 어떻게 발생하는지에 대한 명시적인 지침을 쉽게 제공할 수 있습니다.
브라우저의 네트워크 탭을 열면 많은 활동을 확인할 수 있습니다. 에셋이 다운로드되고, 정보가 제출되고, 이벤트가 기록되는 등 다양한 활동이 이루어지고 있습니다.
이렇게 많은 일이 벌어지는 상황에서 트래픽의 우선순위를 효과적으로 관리하는 것은 매우 중요합니다. 대역폭 경합은 실제로 발생하며, 모든 HTTP 요청이 동시에 발생할 때, 일부 요청은 다른 요청보다 우선순위가 낮습니다. 예를 들어, 둘 중 선택을 해야 한다면 단순히 무언가 시도했다는 애널리틱스 요청보다는 누군가의 결제 요청이 성공적으로 완료된 것을 더 선호할 것입니다. 그리고 페이지 하단의 로고를 렌더링하는 것 보다 히어로 이미지가 빨리 표시되도록 하는 것이 더 중요할 것입니다.
역주: 히어로 이미지는 웹 사이트 상단에 있는 대형 배너 이미지를 설명하는 데 사용되는 웹 사이트 디자인 용어입니다. '히어로 헤더'라고도 불리는 이 헤더는 일반적으로 전체 너비로 확장되는 웹페이지 상단에 눈에 띄게 되어 사용자가 회사와 제품을 처음으로 엿볼 수 있도록 합니다.(https://www.optimizely.com/optimization-glossary/hero-image/)
다행히도 브라우저에는 이 모든 네트워크 활동의 우선순위를 정하는 데 도움이 되는 도구들이 점점 더 많아지고 있습니다. "우선순위 힌트"는 브라우저 리소스가 제한될 때 어떤 요청을 다른 요청보다 우선적으로 처리해야 하는지에 대한 가정을 줄이고, 더 명확한 결정을 내릴 수 있도록 도와줍니다.
이러한 유용한 도구들은 잘 활용하면 점점 더 중요해지고 있는 핵심 웹 바이탈을 포함한 페이지 성능에 현저한 영향을 미칠 수 있습니다. 몇 가지 도구와 함께 가장 유용한 시나리오를 살펴보겠습니다.
최신 브라우저에서는 <link rel="preload" ... />
과 같은 형태로 현재 페이지에 필요한 리소스에 대해 알려주는 기능이 잘 지원되고 있습니다. 이것을 문서의 <head>
에 배치하면 브라우저는 "높음" 우선순위로 가능한 한 빨리 다운로드를 시작하라는 지시를 받습니다.
공정하게 말하자면, 브라우저의 프리로드 스캐너는 이미 이런 종류의 작업에 매우 능숙합니다(이전에 이에 대해 좀 더 자세히 설명한 적이 있습니다). 따라서 프리로딩은 늦게 발견된 에셋, 즉 HTML에서 직접 로드되지 않은 에셋에 사용하는 것이 가장 좋습니다. 예를 들어 일부 CSS 내에 로드된 글꼴 파일이나 인라인 style
속성을 통해 로드된 배경 이미지를 생각해 보세요.
예: 기본적으로 Chrome은 매우 높은 우선순위로 글꼴을 로드하지만 네트워크 연결 속도가 느린 경우 대체 글꼴을 사용하고 우선순위를 낮춥니다(참고로, 저도 처음에는 이 동작에 대해 잘못 알고 글꼴의 우선순위가 항상 '가장 낮은' 것으로 생각했습니다. 로빈 마르크스와 논의하기 전까지는요.).
CSS @font-face
규칙을 통해서만 로드되는 글꼴을 예로 들어보겠습니다.
@font-face {
font-family: "Inter Variable";
src: url("./font.woff2") format("woff2");
}
페이지의 시각적 경험에 매우 중요한 글꼴임에도 불구하고, 로드 시 해당 글꼴은 다운로드 우선순위가 가장 낮게 설정됩니다.
이제 해당 리소스를 프리로드해 보겠습니다.
<head>
<!-- 기타 항목... -->
<link rel="preload" href="/font.woff2" as="font">
</head>
이제 훨씬 더 우선순위가 높습니다.
더욱 명시적으로 표시 하려면, link
태그에 직접 fetchpriority
를 사용할 수도 있습니다. 이를 통해 한 번에 여러 에셋을 프리로드할 때 상대적인 우선순위를 지정할 수 있습니다.
다음은 두 개의 글꼴을 프리로드하되 한 글꼴을 다른 글꼴보다 우선적으로 로드하는 가상의 시나리오입니다.
<link rel="preload" href="./font-1.woff2" as="font" fetchpriority="low" />
<link rel="preload" href="./font-2.woff2" as="font" fetchpriority="high" />
결과적으로 네트워크 활동에 이러한 지침이 반영됩니다.(프리로드되지 않은 세 번째 글꼴도 여기에 포함시켰습니다.)
일반적으로 에셋이 HTML에서 직접 로드되지 않지만 페이지 경험에 중요한 에셋(글꼴, CSS 배경 이미지 등)인 경우 프리로딩을 사용합니다. 또한 같은 유형의 리소스를 여러 개 프리로드할 때 fetchpriority
속성을 포함하면 어떤 리소스가 가장 중요한지 명확하게 알 수 있습니다.
fetch()
요청 우선순위 지정제 생각에 Fetch API는 최신 웹에서 가장 사용자 친화적으로 설계된 것 중 하나입니다. 또한 발신 요청에 우선순위를 표시하는 기능과 같이 XMLHttpRequest
에는 없는 몇 가지 멋진 기능도 있습니다.
가장 주목할 만한 사용 사례는 이미 언급했던 애널리틱스 요청입니다. 대역폭이 부족하고 여러 요청이 동시에 처리되는 경우 브라우저는 자체적으로 우선순위를 결정합니다. 하지만 엔지니어로서 우리는 일반적인 애널리틱스 요청보다 페이지의 목적에 더 중요한 다른 요청에 우선순위를 둬야 한다는 것을 알고 있습니다. 최신 fetch()
는 이를 쉽게 가능케 해줍니다.
다음은 두 개의 요청을 사실상 동시에 대기열에 넣는 간단한 가상의 상황입니다.
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
});
기본적으로 브라우저는 두 가지를 모두 "높은" 우선순위로 자동 설정합니다.
이제 브라우저에 각 요청의 우선순위를 지정하는 방법을 명시적으로 알려주겠습니다.
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
+ priority: "high"
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
+ priority: "low"
});
이번에는 우선 순위가 다릅니다.
여기서 우려할 수 있는 점은 사용자가 너무 빨리 페이지를 벗어나면 우선순위가 "낮은" 요청이 무효 처리되어 취소될 수 있다는 점입니다. 이는 현실적인 문제입니다. 몇 가지 요인에 따라 탭을 닫거나 다음 페이지로 이동하면 중요하지만 상대적으로 우선순위가 낮은 요청이 중단될 수 있습니다.
다행히도 fetch()
는 keepalive
옵션도 허용합니다. true
로 설정하면 페이지가 종료되더라도 브라우저는 해당 요청을 중단하지 않고 수행합니다. 더 자세히 알아보고 싶으시다면 얼마 전 CSS-Tricks에 작성한 글을 참고하세요.
여러 요청이 동시에 실행되고 있고 어떤 요청이 가장 중요한지(또는 안전하게 우선순위를 낮출 수 있는 요청인지) 명확하게 알 수 있는 경우 fetch()
의 우선순위를 지정하세요.
<img />
요청 우선 순위 지정우리가 특별한 조치를 취하지 않아도 브라우저는 페이지에서 가장 중요한 이미지를 결정하기 위해 최선을 다합니다. 이를 설명하기 위해 다음 이미지를 충분한 간격을 두고 로드하여 한 개만 "접힌 부분 위에" 표시되도록 했습니다.
<img src="./cat-1.jpeg" />
<div style="height: 5000px"></div>
<img src="./cat-2.jpeg" />
<div style="height: 5000px"></div>
<img src="./cat-3.jpeg" />
브라우저는 어떤 것이 가장 중요한지 알아냈지만 몇초가 걸렸습니다. 다운로드가 시작되었을 때 세 가지 모두 우선순위가 "낮음"이었습니다. 하지만 얼마 지나지 않아 가장 위에 있는 것이 "높음"으로 바뀌었습니다.
첫 번째 이미지에 fetchpriority
속성을 추가하면 브라우저가 예측 가능해집니다.
<img src="./cat-1.jpeg" fetchpriority="high" />
그 후, cat-1.jpeg
가 가장 높은 우선순위로 로드되었습니다. 처음에는 의아할 수 있지만, 이는 당연한 결과입니다. 브라우저는 리소스 중요도를 판단하는 데 능숙하지만 명확한 지침이 있으면 도움이 됩니다. 이미지가 중요하다는 것을 알고 있다면 명확하게 설명하세요.
참고로 이 기능은 요즘 많이 지원되는 기본 이미지 지연 로딩과 매우 잘 어울립니다.
<img src="./cat-1.jpeg" fetchpriority="high"/>
<div style="height: 5000px"></div>
<img src="./cat-2.jpeg" loading="lazy" />
<div style="height: 5000px"></div>
<img src="./cat-3.jpeg" loading="lazy" />
이를 통해 브라우저는 적절한 경우에만 이미지를 로드하는 방법과 로드할지 여부를 정확히 파악합니다. 제 경우에는 처음 로드할 때 오프스크린 이미지 요청은 시작되지도 않습니다. 대신 이미지가 뷰포트에 가까워질 때까지 기다립니다.
이미지가 페이지 경험에 매우 중요하다는 것을 알고 있다면 이미지에 명시적으로 fetchpriority
를 사용하세요. 히어로 이미지는 도입해보기에 좋은 곳이며, 페이지의 핵심 웹 바이탈, specifically LCP
(가장 콘텐츠가 많은 페인트)에도 영향을 미칠 수 있습니다.
<script />
태그 우선 순위 지정페이지에 src
속성이 있는 일반 <script />
는 가져올 때 우선순위가 high
지만, 페이지가 로드되고 실행될 때까지 나머지 페이지의 구문 분석이 차단된다는 단점이 있습니다. 이러한 이유로 async
속성이 유용합니다. 우선순위 low
로 백그라운드에서 스크립트를 요청하고 스크립트가 준비되는 즉시 실행합니다. 이를 알면 다음 설정이 예상대로 작동합니다.
<script src="/script-async.js" async onload="console.log('async')"></script>
<script src="/script-sync.js" onload="console.log('sync')"></script>
<script>console.log("inline");</script>
비동기 스크립트의 우선 순위가 내려갑니다.
그리고 콘솔은 async
스크립트가 로드되는 동안 후속 스크립트가 구문 분석 및 실행되도록 허용되었는지 확인합니다.
대부분의 경우 이러한 동작은 괜찮습니다. 하지만 때로는 스크립트를 비동기적으로 "높은" 우선순위로 로드하고 싶을 수도 있습니다.
가능한 시나리오는 랜딩 페이지의 히어로에 탑재된 작은 SPA입니다. 페이지의 핵심 웹 바이탈, 특히 LCP와 FID(첫 번째 입력 지연, 곧 다음 페인트로의 인터렉션으로 대체됨)를 보존하려면 해당 스크립트의 우선순위를 높게 지정해야 합니다(결국 이 스크립트는 애플리케이션을 구축하고 제공하는 역할을 담당하기 때문입니다). 하지만 동시에 이 스크립트가 페이지의 나머지 부분을 파싱하는 데 방해가 되어서는 안 됩니다.
따라서 fetchpriority
를 지정해 보겠습니다.
+ <script src="/script-async.js" async onload="console.log('async')" fetchpriority="high"></script>
<script src="/script-sync.js" onload="console.log('sync')"></script>
<script>console.log("inline");</script>
이제 나머지 페이지를 차단하지 않으면서도 높은 우선순위로 다운로드됩니다.
그리고 콘솔에서 이를 확인합니다. 우선순위가 높을수록 비동기 스크립트가 더 빨리 로드됩니다. 이 경우 동기식 및 인라인 스크립트보다 먼저 로드됩니다.
여기서는 구체적으로 다루지 않았지만, fetchpriority
는 지연된 스크립트에서도 작동합니다.
스크립트의 우선순위를 미리 알고 있고 브라우저가 스스로 판단할 수 있는 정보가 충분하지 않다고 의심되는 경우 스크립트에 fetchpriority
를 설정하세요. 앞서 언급했듯이 논 블로킹, 비동기 방식으로 로드하려는 스크립트의 우선순위를 지정하는 데 특히 유용합니다.
이러한 도구에 지나치게 열중하다 보면 남용으로 이어지기 쉽습니다. 그러니 주의하세요. 그렇게 하면 대가를 치를 수 있습니다. "모든 것을 강조하는 것은 아무것도 강조하지 않는 것과 같다."라는 속담이 있듯 실제로 과도하게 사용하면 브라우저가 네트워크 경합을 관리하기가 더 어려워져 페이지의 성능이 저하될 수 있습니다.
MDN은 우선순위 힌트 문서에서도 이 점을 강조하고 있습니다.
브라우저가 리소스를 자동으로 로드하는 최선의 방법을 유추할 수 없는 예외적인 경우에만 제한적으로 사용하세요. 과도하게 사용하면 성능이 저하될 수 있습니다.
따라서 이러한 도구가 존재한다고 해서 무조건 사용해야 한다는 의무감을 느끼지 마세요. 조심해서 사용하세요.
매우 다양한 상황이 있기 때문에 우선순위 힌트를 활용할 수 있는 몇 가지 경우를 간단히 살펴보겠습니다. 모든 경우를 다 다루지 않고, 시작하기에 좋은 몇 가지를 소개하겠습니다.
fetch()
요청을 사용할 수 있습니다.브라우저는 페이지를 작동시키는 콘텐츠를 다운로드하는 방법과 시기를 알아내는 데 매우 똑똑합니다. 하지만 항상 좋은 것은 아닙니다. 페이지가 존재하는 이유나 개별 부분의 의도를 알지 못합니다. 그래서 때때로 추가적인 도움이 필요할 수 있습니다.
그렇기 때문에 이러한 우선순위 힌트가 존재하는 것입니다. 지침을 명확하게 하고 브라우저에서 잘못된 결정을 내릴 가능성을 최소화하기 위해서입니다. 다음에 애플리케이션의 네트워크 활동을 분석할 때 이 힌트를 염두에 두고, 사용이 유의미할 때 페이지 성능을 조금 더 영리하게 개선하는 데 도움이 되는 힌트를 활용하세요.
🚀 한국어로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!