성능 최적화의 상징인 라이트하우스(Lighthouse) 점수가 100점인데도, 사용자는 여전히 “웹사이트가 클릭해도 반응이 늦고 답답하다”고 느낀다면 무엇이 문제일까요? 많은 개발자가 로딩 속도(LCP)를 줄이는 데 집중하지만, 정작 사용자가 페이지에 들어온 이후의 ‘반응성’에는 소홀한 경우가 많아요.
웹 사이트가 비대해지고 복잡한 비즈니스 로직이 브라우저로 넘어오면서, 이제 우리는 단순히 데이터를 빨리 보여주는 것을 넘어 ‘사용자의 상호작용에 얼마나 즉각적으로 화답하는가’를 고민해야 합니다. 오늘은 2026년 현재, 프론트엔드 성능의 핵심 지표인 INP(Interaction to Next Paint)를 정복하고, 브라우저의 메인 스레드를 효율적으로 관리할 수 있는 Scheduler API 활용법에 대해 깊이 있게 이야기해 보려고 해요.
1. INP(Interaction to Next Paint): FID를 넘어선 새로운 기준
불과 몇 년 전까지만 해도 우리는 FID(First Input Delay)를 최적화하는 데 열을 올렸죠. 하지만 FID는 사용자의 ‘첫 번째’ 클릭에만 집중한다는 한계가 있었어요. 사용자는 페이지를 이용하는 내내 수많은 클릭과 스크롤, 입력을 반복하는데 말이죠.
그래서 등장한 것이 바로 INP입니다. INP는 사용자가 페이지를 방문하는 동안 발생하는 모든 상호작용의 지연 시간을 측정하여, 그중 가장 긴 응답 시간을 대표값으로 설정해요.
- 왜 중요할까요?: 사용자는 100ms 이내에 피드백이 오지 않으면 “느리다”고 인지하기 시작해요. 200ms가 넘어가면 흐름이 끊긴다고 느끼고, 500ms 이상이면 서비스에 대한 신뢰를 잃게 됩니다.
- 어떻게 개선하나요?: 핵심은 메인 스레드가 ‘긴 작업(Long Tasks)’에 묶여 있지 않도록 만드는 것입니다. 브라우저가 사용자의 클릭을 감지했을 때, 즉시 그 클릭에 반응할 수 있는 여유를 만들어줘야 하죠.
2. 메인 스레드의 한계와 자바스크립트의 실행 구조
자바스크립트는 싱글 스레드 언어라는 점, 다들 알고 계실 거예요. 브라우저의 메인 스레드는 화면을 그리는 레이아웃 작업, 페인팅, 그리고 우리가 짠 자바스크립트 코드를 실행하는 일을 혼자서 다 처리합니다.
만약 여러분의 코드에 500ms 동안 돌아가는 복잡한 데이터 필터링 로직이 있다면, 그동안 브라우저는 사용자가 버튼을 눌러도 반응할 수 없어요. 마치 1차선 도로에 거대한 덤프트럭이 가로막고 있어 뒤따라오는 승용차들이 꼼짝 못 하는 상황과 같죠.
핵심 요약 > “사용자의 상호작용은 승용차이고, 무거운 연산 작업은 덤프트럭입니다. 우리가 해야 할 일은 이 덤프트럭을 작은 조각으로 나누어 승용차가 지나갈 길을 터주는 것입니다.”
3. Scheduler API: 브라우저 스케줄링의 지휘자
그동안 우리는 작업을 쪼개기 위해 setTimeout이나 requestIdleCallback 같은 편법을 사용해 왔어요. 하지만 이는 정교한 제어가 어렵고 브라우저의 우선순위와 충돌하는 경우가 많았죠.
2026년 현재, 현대적인 브라우저는 Scheduler API를 통해 이를 아주 우아하게 해결합니다. 그중 핵심인 scheduler.postTask()는 작업에 명확한 우선순위를 부여할 수 있게 해줘요.
세 가지 우선순위 레벨
- user-blocking: 화면 렌더링이나 클릭 반응처럼 즉시 처리해야 하는 작업 (최우선)
- user-visible: 사용자에게 보이지만 약간의 지연은 허용되는 작업 (기본값)
- background: 로그 전송, 캐시 업데이트 등 당장 급하지 않은 작업 (최하위)
4. ‘Yielding’의 미학: 긴 작업을 쪼개는 기술
Scheduler API의 꽃은 바로 scheduler.yield()입니다. 이 메서드는 실행 중인 자바스크립트 함수가 잠시 멈추고 “브라우저야, 혹시 급하게 처리해야 할 클릭이나 렌더링 작업이 있니? 있다면 먼저 하고 와!”라고 양보하는 역할을 해요.
실전 코드 예시
비대한 리스트를 처리하는 로직을 가정해 볼게요.
async function processLargeData(items) {
for (const item of items) {
// 무거운 연산 수행
performHeavyCalculation(item);
// 5개 아이템마다 브라우저에게 메인 스레드 양보
if (shouldYield()) {
await scheduler.yield();
}
}
}
이렇게 yield()를 사용하면, 긴 루프 중간에 사용자가 버튼을 클릭하더라도 브라우저가 그 클릭을 감지하고 반응할 수 있습니다. 결과적으로 INP 지표가 비약적으로 개선되죠.
5. 실제 시나리오: 복잡한 데이터 대시보드 개선하기
제가 멘토링했던 한 프로젝트에서는 수만 개의 데이터 포인트를 실시간으로 렌더링하는 대시보드를 개발 중이었어요. 차트를 그리는 동안 필터 버튼을 누르면 화면이 2초간 멈추는 현상이 발생했죠.
해결 방법은 다음과 같았습니다:
- 초기 렌더링:
user-blocking우선순위로 차트의 뼈대를 먼저 그립니다. - 데이터 상세 계산:
user-visible우선순위로 설정하고, 100개 데이터마다scheduler.yield()를 호출하여 스레드를 점유하지 않도록 했습니다. - 부가 정보 로딩:
background우선순위로 툴팁이나 상세 로그 정보를 가져오도록 처리했습니다.
이 결과, 메인 스레드의 최대 점유 시간이 2,000ms에서 80ms로 줄어들었고, 사용자는 데이터가 로딩되는 중에도 자유롭게 필터를 조작할 수 있게 되었어요.
6. UX 관점에서의 ‘반응성’ 재정의
기술적인 수치도 중요하지만, 사용자가 느끼는 심리적 반응성도 무시할 수 없어요. 단순히 작업을 쪼개는 것에서 나아가, ‘피드백의 즉각성’을 설계해야 합니다.
- 낙관적 피드백: 서버의 응답을 기다리기 전, 메인 스레드에서 즉시 UI 상태를 업데이트하세요.
- 스켈레톤의 진화: 단순히 회색 박스를 보여주는 게 아니라, 실제 데이터의 레이아웃과 일치하는 스켈레톤을
user-blocking작업으로 처리하여 시각적 안정성을 높이세요. - 의도 감지: 사용자가 버튼 위에 마우스를 올리는 순간(
mouseenter),background우선순위로 필요한 리소스를 미리 가져오기 시작하세요.
7. 마무리: 사용자에게 ‘즉각적인’ 경험을 선물하세요
2026년의 웹은 더 이상 정적인 문서가 아닙니다. 하나의 복잡한 애플리케이션이죠. 개발자인 우리는 코드의 효율성뿐만 아니라 ‘브라우저가 어떻게 쉬어야 하는지’도 설계할 줄 알아야 합니다.
오늘의 내용을 요약해 볼까요?
- INP는 사용자가 페이지를 이용하는 내내 느끼는 반응성을 측정하는 가장 중요한 지표입니다.
- 메인 스레드를 독점하는 긴 작업은 사용자 경험을 망치는 주범입니다.
- Scheduler API의
postTask와yield를 사용해 작업의 우선순위를 정교하게 관리하세요. - 기술적 최적화는 결국 사용자가 “이 서비스는 내 손가락 끝에 즉각 반응한다”는 신뢰를 얻기 위함임을 기억하세요.
여러분의 프로젝트에서도 지금 바로 메인 스레드의 점유 시간을 체크해 보세요. 아주 작은 ‘양보(Yielding)’ 하나가 사용자의 이탈을 막는 결정적인 한 끗 차이가 될 수 있습니다. 궁금한 점이 있다면 언제든 고민을 나눠주세요!