Next.js Analytics 정리

Next.js Docs – App Router Guides: Analytics 페이지를 기반으로, Next.js 애플리케이션에 성능 분석 및 웹 바이탈(Web Vitals) 분석을 추가하는 방법을 정리한 문서입니다.


1. Next.js Analytics 개요

Next.js는 기본적으로 웹 성능 측정 및 보고(Analytics) 를 지원합니다.

  • useReportWebVitals 훅을 사용하여 직접 성능 지표를 수집·보고할 수 있습니다.
  • 또는 Vercel에서 제공하는 관리형 Analytics 서비스를 사용하여, 별도 코드 작성 없이도 성능 지표를 수집하고 대시보드에서 시각화할 수 있습니다.

이 가이드에서는 주로 직접 구현하는 방식(“Build Your Own”) 에 초점을 맞추어 정리합니다.


2. Client Instrumentation

보다 고급 분석 및 모니터링을 위해, Next.js는 클라이언트 인스트루멘테이션 파일을 지원합니다.

  • 파일 이름: instrumentation-client.js 또는 instrumentation-client.ts
  • 위치: 애플리케이션의 루트 디렉터리 (예: app과 같은 최상위 수준)
  • 실행 시점: 애플리케이션의 프런트엔드 코드가 실행되기 이전

이를 통해 다음과 같은 작업을 전역적으로 설정할 수 있습니다.

  • 분석 도구 초기화 (예: Sentry, Datadog, 자체 Analytics 등)
  • 전역 에러 추적 로직 설정
  • 성능 모니터링 스크립트 주입

2.1 기본 예시

// instrumentation-client.js

// 애플리케이션 시작 전에 Analytics 초기화
console.log('Analytics initialized')

// 전역 에러 추적 설정
window.addEventListener('error', (event) => {
  // 에러 추적 서비스로 전송
  reportError(event.error)
})

이 파일은 Next.js에 의해 자동으로 불러와지며,
별도 import 없이도 애플리케이션 전역에 적용되는 클라이언트 초기화 코드로 동작합니다.


3. 직접 구현하는 Analytics (Build Your Own)

Next.js는 성능 측정을 위해 next/web-vitals 모듈에서 useReportWebVitals을 제공합니다.

// app/_components/web-vitals.js
'use client'

import { useReportWebVitals } from 'next/web-vitals'

export function WebVitals() {
  useReportWebVitals((metric) => {
    console.log(metric)
  })
}

그리고 이 컴포넌트를 루트 레이아웃에서 렌더링하면, 페이지 전역에 대해 웹 바이탈 측정이 활성화됩니다.

// app/layout.js
import { WebVitals } from './_components/web-vitals'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <WebVitals />
        {children}
      </body>
    </html>
  )
}

useReportWebVitals 훅은 'use client' 지시문이 필요하므로,
가장 좋은 방법은 이 훅을 사용하는 작은 클라이언트 컴포넌트 하나(WebVitals) 를 만들고,
루트 레이아웃에서 import 하여 사용하는 것입니다.
이렇게 하면 클라이언트 경계가 WebVitals 컴포넌트에만 한정되어, 불필요한 클라이언트 번들 확대를 피할 수 있습니다.


4. Web Vitals란 무엇인가

Web Vitals 는 사용자 경험을 잘 반영하는 핵심 웹 성능 지표들의 모음입니다.
Next.js의 useReportWebVitals 훅은 다음과 같은 지표들을 모두 수집합니다.

  • TTFB (Time to First Byte)
    서버에서 첫 바이트가 응답될 때까지 걸리는 시간
  • FCP (First Contentful Paint)
    화면에 처음으로 텍스트/이미지 등 콘텐츠가 그려지는 시점
  • LCP (Largest Contentful Paint)
    페이지에서 가장 큰 콘텐츠 요소가 렌더링 완료되는 시점
  • FID (First Input Delay)
    사용자가 처음 상호작용(클릭 등)을 했을 때, 브라우저가 그 입력에 반응하기 시작할 때까지의 지연
  • CLS (Cumulative Layout Shift)
    예기치 않은 레이아웃 변화의 정도를 측정하는 지표
  • INP (Interaction to Next Paint)
    상호작용 이후 다음 화면 업데이트까지의 응답성을 측정하는 지표

각 측정 결과는 metric.name 속성으로 구분할 수 있습니다.

4.1 특정 지표별로 분기 처리

// app/_components/web-vitals.tsx
'use client'

import { useReportWebVitals } from 'next/web-vitals'

export function WebVitals() {
  useReportWebVitals((metric) => {
    switch (metric.name) {
      case 'FCP': {
        // FCP 결과 처리
        break
      }
      case 'LCP': {
        // LCP 결과 처리
        break
      }
      case 'CLS': {
        // CLS 결과 처리
        break
      }
      // 나머지 지표들도 필요에 따라 분기
      default: {
        // 공통 처리
      }
    }
  })
}

이처럼 switch (metric.name) 패턴을 사용하면,
각 지표를 원하는 방식으로 분류하여 저장·전송·로그 등을 수행할 수 있습니다.


5. 외부 시스템으로 지표 전송하기

실제 서비스에서는 Web Vitals 결과를 외부 분석 시스템으로 보내어 저장·시각화하는 경우가 많습니다.

예시: navigator.sendBeacon 또는 fetch를 사용한 전송

useReportWebVitals((metric) => {
  const body = JSON.stringify(metric)
  const url = 'https://example.com/analytics'

  // sendBeacon이 지원되면 이를 우선 사용
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, body)
  } else {
    fetch(url, {
      body,
      method: 'POST',
      keepalive: true,
    })
  }
})
  • navigator.sendBeacon은 페이지 언로드 시점에도 데이터를 안정적으로 전송할 수 있어,
    • 성능 측정 이벤트 전송에 적합합니다.
  • keepalive: true 옵션은 브라우저가 요청을 백그라운드에서도 계속 처리하도록 돕습니다.

5.1 Google Analytics 예시

Google Analytics를 사용하는 경우, metric.id 값 등을 활용하여 분포(퍼센타일 등) 를 직접 계산할 수도 있습니다.

useReportWebVitals((metric) => {
  // window.gtag가 초기화되어 있다고 가정
  // (공식 예제: examples/with-google-analytics 참고)
  window.gtag('event', metric.name, {
    value: Math.round(
      metric.name === 'CLS' ? metric.value * 1000 : metric.value
    ), // GA 이벤트 값은 정수여야 함
    event_label: metric.id, // 특정 세션/측정 구분에 활용
  })
})
  • CLS는 매우 작은 소수값이기 때문에, GA에서 정수로 다루기 위해 * 1000을 곱한 뒤 반올림합니다.
  • metric.id를 활용하면 메트릭 분포를 직접 계산하거나 특정 세션/페이지와 연결할 수 있습니다.