[웹 성능 최적화] Interaction to Next Paint (INP) 개선: 사용자 경험을 극대화하는 실전 전략
새로운 Core Web Vitals 지표, Interaction to Next Paint(INP)를 깊이 이해하고 실제 웹 애플리케이션에서 INP 점수를 개선하는 실용적인 최적화 전략들을 블루가 자세히 알려드려요. 사용자 인터랙션 응답성을 높여 진정한 사용자 경험을 만들어 보세요.
정보🤖 이 포스팅은 Gemini 2.5 Flash AI가 작성했어요.
내용의 정확성을 위해 검토를 거쳤지만, 실무 적용 전 공식 문서를 함께 참고해 주세요.
유용한 팁Interaction to Next Paint(INP)는 새로운 Core Web Vitals 지표로, 사용자 인터랙션에 대한 웹 페이지의 응답성을 측정해요. 이 글에서는 INP의 중요성을 이해하고, 실제 웹 애플리케이션에서 INP 점수를 개선하기 위한 실용적인 측정 및 최적화 전략들을 자세히 알려드려요.
안녕하세요, 10년 이상 경력의 시니어 풀스택 개발자 블루입니다.
오늘은 웹 성능 최적화 분야에서 새롭게 주목받고 있는 지표인 **Interaction to Next Paint (INP)**에 대해 이야기해 보려고 해요. INP는 사용자 경험에 직접적인 영향을 미치기 때문에, 이 지표를 이해하고 개선하는 것은 현대 웹 개발에서 필수적이라고 할 수 있어요.
🤔 INP, 왜 중요할까요?
0️⃣ INP란 무엇인가요?
INP는 Interaction to Next Paint의 약자로, 사용자가 페이지와 상호작용하기 시작한 시점부터 다음 프레임이 화면에 그려지기까지 걸리는 시간을 측정하는 Core Web Vitals 지표예요.
쉽게 말해, 사용자가 버튼을 클릭하거나 텍스트를 입력했을 때, 웹 페이지가 얼마나 빠르게 시각적인 반응을 보여주는지를 측정하는 지표라고 생각하시면 돼요.
정보INP의 좋은 점수 기준: 200밀리초(ms) 이하
개선 필요: 200ms ~ 500ms
나쁜 점수: 500ms 초과
1️⃣ 왜 INP가 중요한가요? (FID 대체)
INP는 2024년 3월부터 기존의 First Input Delay (FID) 지표를 대체하는 새로운 Core Web Vitals 지표로 선정되었어요. FID는 첫 번째 상호작용의 지연 시간만을 측정했지만, INP는 페이지의 전체 수명 주기 동안 발생하는 모든 상호작용의 응답성을 측정한다는 점에서 더 포괄적이고 사용자 경험을 정확하게 반영해요.
사용자들은 웹 페이지가 자신의 입력에 즉각적으로 반응하기를 기대해요. 반응이 느리면 답답함을 느끼고, 결국 페이지를 떠나게 될 가능성이 높죠. INP는 이러한 사용자의 '느낌'을 수치화하여 개발자들이 개선할 수 있도록 돕는 아주 중요한 지표가 된답니다.
2️⃣ 나쁜 INP 점수의 주요 원인
INP 점수가 나쁘다는 것은 사용자의 인터랙션에 대한 페이지의 응답성이 낮다는 의미예요. 주로 다음과 같은 원인들이 있어요.
- 장기 실행 JavaScript 작업: 사용자의 입력 이벤트 핸들러 내부나 메인 스레드에서 너무 긴 JavaScript 작업이 실행될 때, 브라우저는 렌더링을 업데이트할 수 없어 지연이 발생해요.
- 과도한 렌더링 업데이트: 인터랙션 이후 DOM 변경이 너무 많거나 복잡하여 브라우저가 화면을 다시 그리는 데 오랜 시간이 걸릴 때 발생해요.
- 입력 지연: 이벤트 핸들러가 등록되지 않았거나, 메인 스레드가 다른 작업으로 바빠서 사용자 입력 이벤트를 즉시 처리하지 못할 때 발생할 수 있어요.
⚙️ INP 개선을 위한 핵심 전략
INP를 개선하기 위해서는 사용자의 인터랙션이 발생했을 때 브라우저의 메인 스레드가 최대한 빠르게 작업을 완료하고 화면을 업데이트할 수 있도록 해야 해요. 다음은 INP 개선을 위한 실질적인 전략들이에요.
0️⃣ INP 측정 방법 (Lab vs Field)
최적화에 앞서 현재 페이지의 INP 점수를 정확히 측정하는 것이 중요해요. 측정 방법은 크게 두 가지로 나눌 수 있어요.
- Field Data (실제 사용자 데이터): 실제 사용자들이 페이지를 사용하는 동안 수집되는 데이터예요. Chrome User Experience Report (CrUX)에서 제공하며, Google Search Console, PageSpeed Insights 등에서 확인할 수 있어요. 가장 현실적인 지표이지만, 즉각적인 피드백을 얻기 어렵다는 단점이 있어요.
- Lab Data (실험실 데이터): 개발 환경에서 시뮬레이션된 조건으로 측정하는 데이터예요. Lighthouse, Chrome DevTools에서 확인할 수 있어요. 즉각적인 피드백을 얻을 수 있고 특정 시나리오를 반복 테스트하기 용이하지만, 실제 사용자 환경과 다를 수 있다는 점을 유의해야 해요.
1️⃣ 장기 실행 작업 최소화
JavaScript는 웹 페이지의 메인 스레드에서 실행되는 경우가 많아요. 메인 스레드가 장시간 다른 작업으로 바쁘면 사용자 인터랙션에 대한 응답이 지연될 수밖에 없어요. 이를 해결하기 위한 전략들이에요.
🅰️ Debounce와 Throttle 적용
빈번하게 발생하는 이벤트(예: 검색 입력, 스크롤, 창 크기 조절)에 대한 핸들러 함수가 너무 자주 실행되지 않도록 제한하는 기법이에요. 이를 통해 불필요한 연산을 줄이고 메인 스레드의 부하를 감소시킬 수 있어요.
유용한 팁Debounce는 특정 시간 내에 추가 입력이 없으면 함수를 실행하고, Throttle은 특정 시간 간격으로만 함수를 실행해요.
🅱️ Web Workers 활용
복잡하고 시간이 오래 걸리는 계산 작업(데이터 처리, 이미지 조작 등)은 메인 스레드에서 분리하여 백그라운드 스레드인 Web Worker에서 실행할 수 있어요. 이를 통해 메인 스레드는 UI 렌더링과 사용자 인터랙션 처리에 집중할 수 있게 된답니다.
🅲 requestIdleCallback으로 작업 분할
requestIdleCallback은 브라우저가 유휴 상태일 때(즉, 메인 스레드가 할 일이 없을 때) 함수를 실행하도록 스케줄링하는 API예요. 우선순위가 낮은 작업을 작은 청크로 분할하여 유휴 시간에 처리함으로써 메인 스레드를 블로킹하지 않고 작업을 수행할 수 있어요.
function processLargeArray(array) { let i = 0; function processChunk() { const chunkSize = 1000; const start = i; const end = Math.min(i + chunkSize, array.length); for (let j = start; j < end; j++) { // 복잡한 계산 수행 console.log(`Processing item ${j}:`, array[j]); } i = end; if (i < array.length) { requestIdleCallback(processChunk); } else { console.log('Array processing complete!'); } } requestIdleCallback(processChunk); } // 예시 사용법 const myLargeArray = Array.from({ length: 10000 }, (_, index) => `item-${index}`); // processLargeArray(myLargeArray); // 메인 스레드를 블로킹하지 않고 처리해요.
2️⃣ 렌더링 업데이트 최적화
인터랙션 발생 후 UI가 업데이트되는 과정도 INP에 영향을 미쳐요. 불필요한 렌더링을 줄이고 효율적으로 화면을 업데이트하는 것이 중요해요.
🅰️ DOM 변경 최소화 및 효율적인 업데이트
React와 같은 라이브러리는 가상 DOM을 사용하여 효율적인 DOM 업데이트를 돕지만, 여전히 불필요하거나 과도한 DOM 변경은 성능 저하를 일으킬 수 있어요.
- Batching (일괄 처리): 여러 상태 업데이트를 한 번의 렌더링으로 묶어서 처리해요. React 18부터는 자동 배치(Automatic Batching)가 대부분의 경우에 적용되어 따로 신경 쓰지 않아도 되지만, 비동기 코드에서는 수동으로
ReactDOM.unstable_batchedUpdates를 사용할 수 있어요 (React 18 이상에서는flushSync로 대체될 수 있지만, 일반적으로 자동 배치에 의존하는 것이 좋아요). - CSS 애니메이션 사용: JavaScript 기반 애니메이션보다 CSS 애니메이션이 브라우저의 최적화를 더 잘 활용할 수 있어요.
transform과opacity속성은 레이아웃과 페인트 단계를 건너뛰고 컴포지트 단계에서 처리되어 성능에 유리해요.
🅱️ requestAnimationFrame 활용
애니메이션이나 시각적인 업데이트를 수행할 때는 requestAnimationFrame을 사용하는 것이 좋아요. 이 API는 브라우저의 다음 리페인트 이전에 콜백 함수를 실행하도록 스케줄링하여, 부드러운 애니메이션과 렌더링을 보장해요.
let start = null; const element = document.getElementById('animate-me'); function animate(timestamp) { if (!start) start = timestamp; const progress = timestamp - start; // 1초 동안 500px 이동하는 애니메이션 const x = Math.min(progress / 1000 * 500, 500); element.style.transform = `translateX(${x}px)`; if (progress < 1000) { requestAnimationFrame(animate); } } // document.getElementById('start-animation-button').addEventListener('click', () => { // start = null; // 애니메이션 시작 시간 초기화 // requestAnimationFrame(animate); // });
3️⃣ 입력 지연 감소
사용자 입력 이벤트가 지연 없이 처리되도록 하는 것도 중요해요.
🅰️ 이벤트 핸들러 최적화
- 이벤트 위임: 많은 요소에 개별적으로 이벤트 핸들러를 등록하는 대신, 부모 요소에 하나의 핸들러를 등록하여 이벤트 버블링을 활용하는 것이 효율적이에요. 이는 메모리 사용량을 줄이고 DOM 트래버설을 최소화해요.
- 불필요한 이벤트 리스너 제거: 컴포넌트가 언마운트될 때나 더 이상 필요 없을 때 이벤트 리스너를 반드시 제거하여 메모리 누수와 불필요한 작업 실행을 방지해야 해요.
import React, { useEffect, useRef } from 'react'; function MyComponent() { const containerRef = useRef<HTMLDivElement>(null); useEffect(() => { const handleClick = (event: MouseEvent) => { if ((event.target as HTMLElement).matches('.clickable-item')) { console.log('Item clicked:', (event.target as HTMLElement).textContent); } }; containerRef.current?.addEventListener('click', handleClick); return () => { containerRef.current?.removeEventListener('click', handleClick); }; }, []); return ( <div ref={containerRef}> <button className="clickable-item">버튼 1</button><br /> <button className="clickable-item">버튼 2</button> </div> ); }
🅱️ CSS Containment 및 Content-visibility 활용
contain CSS 속성은 브라우저에게 특정 요소의 콘텐츠가 다른 요소에 영향을 미치지 않음을 알려 성능 최적화 힌트를 제공해요. content-visibility는 스크린 밖에 있는 콘텐츠의 렌더링 작업을 지연시켜 초기 로딩 성능과 인터랙션 응답성을 크게 향상시킬 수 있어요.
📝 정리
0️⃣ 핵심 요약
Interaction to Next Paint (INP)는 사용자 인터랙션에 대한 웹 페이지의 응답성을 측정하는 중요한 Core Web Vitals 지표예요. INP를 개선하는 것은 사용자 경험을 직접적으로 향상시키는 길이에요.
핵심 전략은 다음과 같아요.
- 장기 실행 JavaScript 작업 최소화:
debounce,throttle, Web Workers,requestIdleCallback등을 활용하여 메인 스레드의 부담을 줄여요. - 효율적인 렌더링 업데이트: DOM 변경을 최소화하고, CSS 애니메이션 및
requestAnimationFrame을 사용하여 부드러운 시각적 피드백을 제공해요. - 입력 지연 감소: 이벤트 위임, 불필요한 이벤트 리스너 제거,
content-visibility와 같은 CSS 최적화 기법을 활용해요.
1️⃣ 다음 액션
여러분의 웹 애플리케이션의 INP 점수를 확인해 보셨나요? PageSpeed Insights나 Chrome DevTools를 활용하여 현재 INP 점수를 측정하고, 오늘 제가 알려드린 전략들을 적용해 보세요.
특히 사용자 인터랙션이 많은 페이지나 컴포넌트부터 개선을 시작하면 좋은 결과를 얻을 수 있을 거예요.
사용자에게 더 빠르고 반응성 좋은 웹 경험을 제공하기 위해 INP 최적화는 이제 선택이 아닌 필수랍니다! 궁금한 점이 있다면 언제든지 댓글로 남겨주세요.
📮 참고
- web.dev: Optimize INP
- web.dev: Interaction to Next Paint (INP)
- MDN Web Docs: Using Web Workers
- web.dev: content-visibility property
![[🤖] JavaScript Decorator 심층 분석: 메타프로그래밍과 타입스크립트 활용 전략 썸네일](/_next/image?url=%2Fimages%2Fblog%2Fjavascript-decorator-thumbnail.png&w=3840&q=75)
![[🤖] 웹 성능 최적화: 리플로우와 리페인트 최소화 전략 썸네일](/_next/image?url=%2Fimages%2Fthumbnails%2Fweb-performance-reflow-repaint.png&w=3840&q=75)
![[🤖] Next.js App Router: generateStaticParams로 동적 라우팅 빌드 최적화하기 썸네일](/_next/image?url=%2Fimages%2Fthumbnails%2Fnextjs-generatestaticparams.png&w=3840&q=75)
![[🤖] React 렌더링 최적화: useMemo, useCallback, React.memo 완벽 가이드 썸네일](/_next/image?url=%2Fimages%2Freact-rendering-optimization-thumbnail.png&w=3840&q=75)
![[🤖] JavaScript Proxy와 Reflect 심층 분석: 메타 프로그래밍으로 코드 강화하기 썸네일](/_next/image?url=%2Fimages%2Fthumbnails%2Fjavascript-proxy-reflect.png&w=3840&q=75)
![[🤖] TypeScript const Type Parameters: 리터럴 타입 추론 강화와 실용적인 활용법 썸네일](/_next/image?url=%2Fimages%2Ftypescript-const-type-parameters-deep-dive.png&w=3840&q=75)
![[🤖] TypeScript 유틸리티 타입 완벽 가이드: 실전 활용 패턴 썸네일](/_next/image?url=%2Fimages%2Ftypescript-utility-types-deep-dive.png&w=3840&q=75)
![[🤖] Next.js Route Handler: App Router에서 안전하고 효율적인 API 구축하기 (인증, 에러 처리 포함) 썸네일](/_next/image?url=%2Fimages%2Fnextjs-route-handler-thumbnail.png&w=3840&q=75)