메인 스레드를 50ms 넘게 붙잡는 작업. W3C Long Tasks 명세는 이벤트 루프 태스크(뒤따르는 마이크로태스크 처리 포함), 렌더링 갱신 단계, 이벤트 루프 밖에서 브라우저가 UI 스레드로 하는 일 가운데 50ms를 넘는 것을 롱 태스크로 본다. 태스크는 도중에 끊기지 않으므로 그동안 클릭·키 입력 처리와 화면 갱신이 모두 밀린다(이벤트 루프).
왜 50ms인가
RAIL 모델(Response, Animation, Idle, Load)은 입력에 100ms 안에 반응하는 것을 목표로 둔다. 근거로는 지연 인식에 관한 UX 연구를 드는데, 링크한 NN/g 글에서 Jakob Nielsen은 0.1초를 시스템이 즉시 반응한다고 느끼는 한계로 본다.
목표가 100ms인데 예산이 50ms인 이유는 입력이 이미 실행 중인 작업 도중에 들어올 수 있어서다. 작업을 50ms 단위로 끊어 두면 입력은 최대 50ms 기다리고, 처리에 남은 50ms를 쓸 수 있다. 명세도 같은 논리로 롱 태스크가 없는 사이트는 100ms 목표를 지킬 수 있다고 설명한다. 다만 web.dev는 지금은 RAIL보다 Core Web Vitals를 성능 목표의 기준으로 권한다.
지표와의 관계
- TBT(Total Blocking Time): FCP(First Contentful Paint) 뒤에 일어난 각 롱 태스크에서 50ms를 넘긴 부분(blocking time)만 더한다. Lighthouse는 기본으로 TTI(Time to Interactive)까지 잰다. 250ms·90ms·35ms 태스크라면 200 + 40 + 0 = 240ms다. 점수에서의 비중은 Lighthouse 성능 점수 읽기에 정리했다
- INP(Interaction to Next Paint): 상호작용 지연은 입력 지연·처리 시간·표시 지연으로 나뉜다. 입력 직전에 시작된 롱 태스크는 이벤트 콜백이 시작되기까지의 입력 지연을 늘린다. 페이지 로드 중에는 큰 스크립트의 파싱·컴파일·실행이 롱 태스크가 되기 쉽다
- TBT는 랩에서 INP 문제를 짐작하는 대리 지표다. 실제 사용자가 겪지 않는 문제를 잡거나, 상호작용이 일으키는 지연을 놓칠 수 있다
찾는 법
PerformanceObserver로 longtask 엔트리(PerformanceLongTaskTiming)를 받는다. buffered: true를 주면 관찰을 시작하기 전에 이미 일어난 롱 태스크도 받는다.
let blocking = 0
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
blocking += entry.duration - 50 // 50ms를 넘긴 부분
console.log(entry.name, entry.duration, entry.attribution)
}
}).observe({ type: 'longtask', buffered: true })이 합은 TBT와 측정 구간이 달라(FCP~TTI로 자르지 않는다) 같은 값이 아니다. 엔트리의 attribution은 어느 컨테이너(최상위 문서, <iframe> 등)에서 일어났는지만 알려 주고, 어떤 스크립트·함수가 원인인지는 알려 주지 않는다.
Chrome DevTools Performance 패널에서는 롱 태스크에 빨간 삼각형이 붙고, 50ms를 넘긴 부분이 빨갛게 칠해진다.
Long Animation Frames(LoAF)
Long Tasks API의 한계는 셋이다. 원인 스크립트를 모른다. 렌더링이 별도 태스크로 돌아 그 렌더링을 일으킨 작업과 함께 세지 않는다. 50ms 아래 태스크 여러 개가 한 프레임을 막는 경우는 잡지 못한다.
LoAF(long-animation-frame 엔트리)는 태스크 대신 렌더링 갱신이 50ms 넘게 늦어진 프레임을 단위로 본다. 렌더링 시간이 포함되고, 5ms 넘게 실행된 스크립트마다 sourceURL·sourceFunctionName·invoker 같은 귀속 정보를 준다. blockingDuration은 프레임 안 롱 태스크에서 50ms를 넘긴 부분을 더한 값이다(가장 긴 태스크에는 렌더링 시간을 더해 계산한다).
지원(2026 기준): LoAF는 Chrome 123부터, Long Tasks API는 Chrome 58부터 쓸 수 있다. 둘 다 Chromium 계열에만 있고 Firefox·Safari는 지원하지 않는다. MDN은 둘 다 실험적 기능으로 표시한다. Chrome 팀은 LoAF가 더 나은 API라고 보면서도 Long Tasks API를 폐기할 계획은 없다고 밝혔다. 지원하지 않는 브라우저를 위해 PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')로 확인한 뒤 관찰한다.
고치는 법
- 작업을 쪼개고 조각 사이에 태스크로 양보한다(
scheduler.yield(), 없으면setTimeout). 코드는 마이크로태스크의 "굶김을 피하려면" 절에 있다. web.dev는isInputPending()은 더 이상 권하지 않고, 입력 대기 여부와 상관없이 양보하라고 한다 - 무거운 계산은 메인 스레드 밖 Web Worker로 옮긴다
- React 렌더가 길면 동시성 렌더링으로 렌더 작업을 잘게 나눈다
실행할 일을 줄이는 것 말고 언제 실행하느냐를 바꾸는 방법도 있다(개인적인 판단). 분석 스크립트처럼 급하지 않은 작업을 스텁 큐로 받아 두고 본체를 하이드레이션 뒤로 미루면, 총 실행 시간은 같아도 첫 화면·하이드레이션 구간의 롱 태스크와 메인 스레드 경합이 빠진다. 다만 미룬 작업도 롱 태스크라서 실행되는 순간 들어온 입력은 똑같이 기다린다. 미룬 시점이 FCP~TTI 안이면 TBT에도 그대로 잡힌다. 실행 시점이 바뀌어 TBT가 움직이는 다른 사례는 Lighthouse 성능 점수 읽기에 있다.
명세: W3C Long Tasks API — Terminology · Introduction
출처: web.dev — RAIL: 50 ms or 100 ms? · NN/g — Response Times: The 3 Important Limits · web.dev — Optimize long tasks · web.dev — Total Blocking Time · web.dev — Optimize INP: input delay · MDN — PerformanceLongTaskTiming · MDN — Long animation frame timing · Chrome — Long Animation Frames API · Status of the API · Will this replace the Long Tasks API? · Chrome DevTools — Performance features reference