2024 Web Almanac에서 중앙값 모바일 페이지의 JavaScript 중 168킬로바이트는 퍼스트파티, 375킬로바이트는 서드파티였습니다.1 평범한 페이지에 있는 스크립트의 절반 이상이, 무게 기준으로, 사이트 소유자가 아닌 누군가가 작성한 것이었습니다. Almanac의 서드파티 장은 페이지의 92%에서 서드파티를 발견했으며, 서드파티 요청 중 가장 흔한 유형은 스크립트였습니다.2 퍼블리셔가 광고를 싣기 위해 추가하는 광고 네트워크의 스크립트도 그런 요청 중 하나입니다. 그 스크립트가 로드 시점에 페이지에 치르게 하는 비용은 스크립트가 무엇을 하느냐보다, 그것을 로드하는 요소가 어떻게 작성되었느냐에 더 많이 달려 있습니다.

브라우저는 속성이 없는 스크립트 요소에서 멈춥니다

HTML 리빙 스탠더드에 따르면 브라우저가 외부 스크립트를 처리하는 방법은 세 가지이며, 차이는 요소에 붙은 속성뿐입니다.3 첫째는 두 속성이 모두 없는 기본 방식으로, 스크립트는 "즉시 가져와 평가되며, 둘 다 완료될 때까지 파싱을 차단합니다." 둘째인 defer는 파싱과 병렬로 파일을 가져오며 "페이지 파싱이 끝났을 때" 평가합니다. 셋째인 async에서도 파일은 병렬로 가져오지만 "사용할 수 있게 되는 즉시(파싱이 완료되기 전일 수도 있음)" 평가됩니다. 파일이 네트워크를 건너오는 동안 파서를 붙잡아 두는 것은 첫 번째 방식뿐이며, 파일이 도착할 때까지 그 요소 뒤의 어떤 것도 그려지지 않습니다.

Google의 Lighthouse 감사는 이 구분을 퍼블리셔가 자기 페이지에서 직접 실행할 수 있는 테스트로 바꿉니다. 이 감사는 문서의 head에 있으면서 defer도 async도 없는 스크립트 요소를 표시합니다.4 보고서는 "페이지의 첫 페인트를 차단하는 모든 URL을 나열합니다." 크기는 이 정의에 포함되지 않으며, 용도도 마찬가지입니다. 분석 스크립트, 광고 스크립트, 글꼴 로더는 속성 없이 head에 있으면 모두 똑같이 취급됩니다.

서드파티 스크립트는 로딩 지침이 염두에 두고 쓰인 경우입니다

web.dev의 지침은 사이트 외부에서 온 스크립트에 대해 분명합니다. 동기식 스크립트는 "DOM 구성과 렌더링을 지연시킨다"고 하며, 여기서 DOM은 브라우저가 문서를 다루는 작업 모델입니다.5 이어지는 규칙은 "페이지가 렌더링되기 전에 실행되어야 하는 스크립트가 아니라면 서드파티 스크립트는 항상 비동기로 로드하라"는 것입니다. async는 로딩 과정 초기에 실행되어야 하는 스크립트에 맞고, defer는 "덜 중요한 리소스"에 맞으며, 그 예로 첫 화면 아래의 동영상 플레이어를 듭니다. 텍스트가 화면에 표시된 후에 작동하는 광고 스크립트는 이 기준에 따르면 덜 중요한 쪽에 속합니다.

이 관행은 널리 퍼졌지만, 모든 페이지에 퍼진 것은 아닙니다. 2024 Web Almanac에 따르면 async를 쓰는 페이지는 2022년 76%에서 87%로, defer를 쓰는 페이지는 42%에서 47%로 늘었습니다.1 이 수치는 페이지 전체를 설명합니다. 자체 스크립트를 지연 로드하는 페이지도 서드파티가 제공한 차단 요소 하나를 여전히 품고 있을 수 있습니다. 설치 코드 조각은 퍼블리셔가 수정하지 않는 한, 네트워크가 작성한 방식대로 로드됩니다.

DOM을 기다리는 스크립트에는 차단할 첫 페인트가 없습니다

브라우저는 파서가 DOM 구축을 마치는 순간을 이벤트로 표시합니다. MDN에 따르면 DOMContentLoaded는 "HTML 문서가 완전히 파싱되고, 모든 지연 스크립트"가 다운로드되어 실행되었을 때 발생합니다.6 이 이벤트는 이미지, 하위 프레임, async 스크립트를 기다리지 않습니다. 따라서 그 순간에 작업을 시작하는 스크립트는, 마지막 이미지를 기다리지 않고, 텍스트가 존재한 후에 시작합니다.

AdBubbles 임베드 adbubbles.js는 페이지의 DOM이 준비된 후에 로드됩니다. 이는 Cloudflare의 캐시를 통해 제공되고 의존성 없는 순수 JavaScript로 작성된 정적 파일 하나입니다. 임베드가 읽는 사이트별 데이터도 캠페인이 바뀔 때 다시 생성되어 엣지에 캐시되는 정적 파일입니다. 따라서 페이지 조회가 광고 서버에 부담을 주지 않으며, 유일한 동적 요청은 작은 비컨뿐입니다. 임베드는 퍼블리셔가 설정한 범위만 검사하며, 제목, 링크, 코드, 서식이 지정된 텍스트, 양식 컨트롤, 내비게이션, 머리글, 바닥글, 그리고 data-noads로 표시된 모든 요소를 건너뜁니다. 설치 안내에 범위 설정이 설명되어 있습니다.

퍼블리셔 계정을 여는 데는 비용이 들지 않으며, 사이트에 임베드를 추가하는 데도 들지 않습니다. 사이트를 추가한 퍼블리셔는 사이트의 어떤 단어가 현재 판매 가능한지 보여 주는 크롤링을 요청할 수 있습니다. 범위, 페이지당 최대 버블 수, 제외할 광고주 카테고리는 같은 계정에서 선택합니다.

독자가 떠날 때 보내는 비컨은 비용의 나머지 절반입니다

독자가 떠나는 순간은 스크립트가 독자를 느리게 만들 수 있는 또 다른 기회입니다. 언로드 시점에 보고하는 분석 코드는 한때 "차단형 동기식 XMLHttpRequest 호출" 같은 기법에 의존했습니다.7 MDN은 그런 방법들이 모두 "문서 언로드를 차단하여 다음 페이지로의 이동을 느리게 한다"고 기록합니다. sendBeacon 메서드가 이를 대체합니다. 이 메서드는 작은 HTTP POST 요청을 비동기로 보냅니다. 브라우저는 데이터를 "언로드나 다음 이동을 지연시키지 않고, 사용자 에이전트가 그럴 기회가 있을 때" 전송합니다.

임베드는 sendBeacon으로 보고하며, 세 종류의 비컨을 보냅니다. 페이지 로드마다 조회 1회를 보냅니다. 버블이 1초 동안 표시되었을 때 열람 1회를, 독자가 버블 안의 링크를 따라갈 때 클릭 1회를 보냅니다. 그 밖에는 아무것도 페이지를 떠나지 않습니다.

AdBubbles는 쿠키를 설정하지 않고, 식별자를 저장하지 않으며, 독자의 개인정보를 수집하지 않습니다. 독자의 국가는 저장된 IP 주소가 아니라 Cloudflare 요청 헤더에서 읽습니다. 퍼블리셔 페이지의 단어를 그 단어를 구매한 광고주와 연결하는 것이 AdBubbles가 하는 일의 전부입니다. AdBubbles는 어떤 사람이 버블을 볼지에 관여하지 않고, 독자를 한 페이지나 사이트에서 다음 곳으로 따라가지 않으며, 어떤 결과도 약속하지 않습니다. 개인정보에 대한 입장은 전문이 공개되어 있습니다.

브라우저는 이제 광고가 치르게 하는 비용을 계산하고 그 합계에 따라 조치합니다

Chrome의 과도한 광고 개입(Heavy Ad Intervention)은 한 브라우저 업체가 무엇을 지나치다고 보는지 보여 줍니다. 광고는 "사용자가 상호작용하지 않은 경우(예: 탭하거나 클릭하지 않은 경우)"에만 과도한 광고로 간주됩니다.8 그 외에 세 가지 기준 중 하나만 넘어도 충분합니다. 첫째는 4메가바이트를 넘는 네트워크 대역폭입니다. 둘째는 30초 동안 15초를 넘는 메인 스레드 사용 시간이고, 셋째는 총 60초를 넘는 사용 시간입니다.

개입이 실행되면 광고 프레임의 콘텐츠는 "광고 삭제됨 메시지로 대체되며", 이 메커니즘은 광고로 태그된 iframe에 작동합니다.8 측정 기준은 바이트와 메인 스레드 시간이며, 광고의 내용은 아무 역할도 하지 않습니다. 버블은 iframe이 아니라 퍼블리셔 자신의 문서 안의 Shadow DOM에서 렌더링되므로, 이 메커니즘은 다른 종류의 광고를 겨냥한 것입니다. 임베드가 특정 페이지에 바이트와 메인 스레드 시간으로 무엇을 더하는지는 그 페이지 자체의 측정이 보여 줄 것입니다.

퍼블리셔가 자기 페이지에서 할 수 있는 점검에는 브라우저 외에 아무것도 필요 없습니다. 페이지 소스에는 head의 모든 스크립트 요소가 나열되며, 각 요소에는 async나 defer가 있거나 둘 다 없습니다. Lighthouse의 개선 기회(Opportunities) 섹션은 둘 다 없는 요소를 첫 페인트를 차단하는 URL로 나열합니다.4 그 목록에 오른 네트워크의 코드 조각은, 네트워크 문서가 그 무게에 대해 무엇이라고 말하든, 표준의 정의에 따라 페이지를 차단하고 있는 것입니다. 광고 스크립트가 그 목록에 오를지는 요소에 붙은 속성이 결정하며, 그 속성은 퍼블리셔가 붙여 넣기 전에 이미 코드 조각에 쓰여 있습니다.