Next.js에서 Core Web Vitals 측정하기: RUM 설정 가이드
Next.js에서 Core Web Vitals를 위한 Real User Monitoring 설정하기 (App Router 및 Pages Router)

Next.js에서 Core Web Vitals 측정하기
Next.js는 매우 빠르고 사용자 친화적인 웹사이트를 구축할 수 있게 해주는 React 기반의 JavaScript 프레임워크입니다. 사실입니다. Next.js는 꽤 빠르며 속도를 유지하기 위한 많은 기능이 내장되어 있습니다. 적어도 이론상으로는 그렇습니다. 시간이 지나고 웹사이트가 커지고 더 많은 기능이 추가되며, 아마도 모든 모범 사례를 따르지 않게 되면 Next.js 페이지는 점점 느려질 것입니다. 그것이 아마도 여러분이 이 페이지를 방문한 이유일 것입니다 :-)
최종 검토: 2026년 3월 Arjen Karel
느린 페이지를 측정하고 예방하려면 Core Web Vitals를 측정하고 지표가 임계값 아래로 떨어질 때 조치를 취하는 것이 중요합니다. 세 가지 Core Web Vitals는 로딩을 위한 Largest Contentful Paint (LCP) (임계값: 2.5초), 상호작용성을 위한 Interaction to Next Paint (INP) (임계값: 200ms), 그리고 시각적 안정성을 위한 Cumulative Layout Shift (CLS) (임계값: 0.1)입니다. 2025 Web Almanac에 따르면 모바일 오리진의 48%만이 세 가지를 모두 통과합니다. 특히 Next.js 사이트의 경우, 2023 Astro 프레임워크 보고서에 따르면 약 25%의 Next.js 오리진만이 통과한 것으로 나타났습니다. Next.js 14 및 15의 Server Components와 App Router는 이를 개선했지만, 현재 상태를 아는 유일한 방법은 자체 필드 데이터를 측정하는 것뿐입니다.
Lighthouse는 잊으세요 (어느 정도는)
Lighthouse는 Core Web Vitals를 위한 테스트 도구입니다. 제가 함께 일하는 거의 모든 클라이언트는 언젠가 자신들의 Lighthouse 점수와 Search Console 점수가 일치하지 않는 것에 대해 이야기하기 시작합니다. 제가 그들에게 가장 먼저 하는 말은 이것입니다. Lighthouse는 잊으세요. 설명해 드리겠습니다.

Lighthouse는 통제된 조건에서 캐시되지 않은 첫 방문의 '실험실 데이터(lab data)'를 수집하는 매우 유용한 도구입니다. 안타깝게도 수집된 데이터가 반드시 필드 데이터를 반영하는 것은 아닙니다. 필드 데이터는 사용자가 페이지를 로드할 때마다 브라우저에 의해 수집됩니다. 그런 다음 해당 데이터가 Google로 전송되어 실제 Core Web Vitals 점수를 결정하는 데 사용됩니다. 이 프로세스는 RUM (Real User Monitoring)이라고도 합니다.
대부분의 개발자가 놓치는 부분은 이것입니다. INP는 실험실 도구에서 전혀 측정할 수 없습니다. Lighthouse는 웹사이트와 상호작용하지 않으므로 상호작용 데이터가 없습니다. Total Blocking Time을 프록시로 사용하지만, TBT와 INP는 동일하지 않습니다. 또한 실험실 도구의 CLS는 Lighthouse가 페이지를 스크롤하거나 클릭하지 않기 때문에 인위적으로 낮은 점수를 보여줍니다. 실제 INP 및 CLS 수치를 얻는 유일한 방법은 실제 사용자로부터 얻는 것입니다.
오해하지 마세요. 저는 Lighthouse를 사랑합니다. 그것은 훌륭한 소프트웨어이며 아마도 구현해야 할 훌륭한 제안을 제공할 것입니다. 제가 말씀드리고 싶은 것은 Next.js 사이트의 RUM 지표가 단지 캐시되지 않은 첫 화면 조회만으로 이루어지는 것이 아니라는 점입니다. 아닙니다. Next.js 웹사이트의 Core Web Vitals는 더 복잡합니다. 그것이 제가 클라이언트를 위해 가장 먼저 구현하는 것 중 하나가 실시간 Real User Monitoring인 이유입니다. Google은 Lighthouse 점수가 아닌 CrUX 필드 데이터를 기준으로 순위를 매깁니다.
Next.js에서 Core Web Vitals 측정하기 (App Router)
App Router(Next.js 13 이상)를 사용하는 경우, Core Web Vitals를 수집하는 권장 방법은 next/web-vitals의 useReportWebVitals 훅을 사용하는 것입니다. 이 훅은 Next.js 자체와 함께 제공되므로 추가로 설치할 필요가 없습니다.
이 훅은 'use client' 지시문이 필요하므로 모범 사례는 이를 작은 컴포넌트로 분리하는 것입니다. 이렇게 하면 클라이언트 경계를 좁게 유지하고 전체 레이아웃을 클라이언트 컴포넌트로 만드는 것을 방지할 수 있습니다.
// app/_components/web-vitals.js
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
console.log(metric)
})
return null
}
그런 다음 루트 레이아웃에서 가져옵니다.
// app/layout.js
import { WebVitals } from './_components/web-vitals'
export default function Layout({ children }) {
return (
<html>
<body>
<WebVitals />
{children}
</body>
</html>
)
}
그게 다입니다. 이제 모든 페이지 로드에서 LCP, INP, CLS, FCP 및 TTFB가 콘솔에 보고됩니다. 물론 프로덕션 환경에서 콘솔에 로깅하는 것은 그다지 유용하지 않습니다. 유용한 곳으로 데이터를 전송하는 방법을 보여드리겠습니다.
사용자 지정 엔드포인트로 Core Web Vitals 전송하기
성능 데이터를 전송하는 가장 안정적인 방법은 navigator.sendBeacon()입니다. 이것은 사용자가 페이지를 벗어날 때 탐색을 차단하지 않고 작은 payload를 전송하기 위해 정확히 설계되었습니다.
// app/_components/web-vitals.js
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
const url = 'https://example.com/analytics'
const body = JSON.stringify(metric)
if (navigator.sendBeacon) {
navigator.sendBeacon(url, body)
} else {
fetch(url, { body: body, method: 'POST', keepalive: true })
}
})
return null
}
Google Analytics(GA4)로 Core Web Vitals 전송하기
gtag 스니펫과 함께 Google Analytics 4를 사용하는 경우 Core Web Vitals를 맞춤 이벤트로 전송할 수 있습니다.
// app/_components/web-vitals.js
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
window.gtag('event', metric.name, {
event_category: 'Web Vitals',
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: true,
})
})
return null
}
기억하세요: RUM 소스에서 Core Web Vitals 데이터를 읽을 때는 75백분위수(75th percentile)를 사용해야 합니다. 이것이 Google이 사용하는 임계값입니다. 평균도 아니고 중앙값도 아닙니다. p75입니다.
트래픽이 많은 사이트에서의 샘플링
트래픽이 많은 웹사이트에서는 모든 사용자의 데이터를 수집하는 것이 별로 의미가 없습니다. 다음과 같이 50% 이하로 샘플링할 수 있습니다.
// app/_components/web-vitals.js
'use client'
import { useReportWebVitals } from 'next/web-vitals'
const inSample = Math.random() >= 0.5
export function WebVitals() {
useReportWebVitals((metric) => {
if (inSample) {
const body = JSON.stringify(metric)
navigator.sendBeacon('/analytics', body)
}
})
return null
}
Next.js에서 Core Web Vitals 측정하기 (Pages Router)
여전히 Pages Router를 사용 중인 경우, Next.js는 pages/_app.js에서 내보내는 내장 reportWebVitals 함수를 제공합니다. 이것은 오래된 접근 방식이지만 여전히 작동합니다.
// pages/_app.js
export function reportWebVitals(metric) {
if (metric.label === 'web-vital') {
console.log(metric)
}
}
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
사용자 지정 엔드포인트나 Google Analytics로 데이터를 전송하는 동일한 패턴이 여기에도 적용됩니다. 훅 콜백 대신 reportWebVitals 함수 내부에 sendBeacon 또는 gtag 호출을 넣기만 하면 됩니다.
참고할 사항: Pages Router는 Next.js-hydration 및 Next.js-route-change-to-render와 같은 Next.js 사용자 지정 지표도 보고합니다. 이는 hydration 및 클라이언트 측 탐색에 걸리는 시간을 알려줍니다. App Router 버전은 아직 이러한 사용자 지정 지표를 보고하지 않습니다.
서드파티 모니터링 도구
Next.js에서 Core Web Vitals를 수집하는 몇 가지 서드파티 도구가 있습니다. Vercel에는 Vercel에 배포하는 경우 기본적으로 작동하는 @vercel/speed-insights가 있습니다. 빠른 개요를 파악하는 데는 좋지만, 지표를 하위 부분으로 나누지 않으며 대시보드는 기본적이고 Vercel 생태계에 종속됩니다.
NewRelic과 Sentry 모두 Core Web Vitals를 수집하지만 성능 도구는 아닙니다. 이들은 Core Web Vitals를 기능으로 덧붙인 오류 추적 및 APM 도구입니다. 둘 다 초기 네트워크 리소스와 메인 스레드 시간을 놓고 경쟁하는 스크립트를 삽입합니다. 저는 Sentry가 모바일에서 200ms의 차단 시간을 추가하는 것을 본 적이 있습니다. 성능 모니터링을 돕기 위해 만들어진 도구로서는 아이러니한 일입니다.
제가 사용하고 추천하는 것은 CoreDash입니다. 이것은 Core Web Vitals를 위해 특별히 구축되었습니다. 모든 지표가 하위 부분으로 분류되어 있으므로 시간이 정확히 어디에 소요되는지 알 수 있습니다. 대시보드는 5개의 메뉴를 클릭하지 않고도 추세, 회귀 및 페이지 수준의 분석을 보여줍니다. 그리고 MCP 통합을 통해 필드 데이터를 AI 도구에 연결하고 일상적인 언어로 성능에 대해 질문할 수 있습니다. "모바일에서 레이아웃 이동(layout shifts)을 유발하는 원인은 무엇인가요"라고 묻기만 하면 자체 데이터에서 답변을 얻을 수 있습니다.
Next.js에서 서드파티 스크립트 문제 수정 또는 렌더링 차단 CSS 제거를 원하신다면, 이에 대한 전용 가이드도 작성해 두었습니다.

