Skip to content

Commit f2fe4c0

Browse files
authored
Merge pull request #291 from manNomi/init/centry
feat: 센트리 적용
2 parents a8ad2c2 + 0f61a3f commit f2fe4c0

5 files changed

Lines changed: 75 additions & 0 deletions

File tree

instrumentation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export async function register() {
2+
if (process.env.NEXT_RUNTIME === "nodejs") {
3+
await import("./sentry.server.config");
4+
}
5+
6+
if (process.env.NEXT_RUNTIME === "edge") {
7+
await import("./sentry.edge.config");
8+
}
9+
}

next.config.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const nextConfig = {
2424
experimental: {
2525
optimizeCss: true,
2626
gzipSize: true,
27+
// Sentry instrumentation 활성화 (Web Vitals 수집에 필요)
28+
instrumentationHook: true,
2729
},
2830
eslint: {
2931
// Warning: This allows production builds to successfully complete even if

sentry.client.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Sentry.init({
55
environment: process.env.SENTRY_ENVIRONMENT || "development",
66
tracesSampleRate: 1.0,
77

8+
// Replay 샘플링 설정
9+
replaysSessionSampleRate: 0.1, // 일반 세션의 10%
10+
replaysOnErrorSampleRate: 1.0, // 에러 발생 시 100%
11+
812
integrations: [
913
Sentry.browserTracingIntegration({
1014
tracePropagationTargets: [
@@ -14,6 +18,8 @@ Sentry.init({
1418
"solid-connect",
1519
/^https:\/\/(www\.)?solid[\-]?connection\.com/,
1620
],
21+
// Web Vitals 자동 수집 활성화
22+
enableInp: true, // Interaction to Next Paint
1723
}),
1824
Sentry.replayIntegration({
1925
maskAllText: true,

src/app/layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import localFont from "next/font/local";
66
import GlobalLayout from "@/components/layout/GlobalLayout";
77
import ToastContainer from "@/components/ui/Toast";
88

9+
import { WebVitals } from "./web-vitals";
10+
911
import { AlertProvider } from "@/context/AlertContext";
1012
import QueryProvider from "@/lib/react-query/QueryProvider";
1113
import "@/styles/globals.css";
@@ -104,6 +106,7 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => (
104106
<body className={pretendard.className}>
105107
<AppleScriptLoader />
106108
<GoogleAnalytics gaId="G-V1KLYZC1DS" />
109+
<WebVitals />
107110
<QueryProvider>
108111
<GlobalLayout>{children}</GlobalLayout>
109112
<ToastContainer />

src/app/web-vitals.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"use client";
2+
3+
import { useReportWebVitals } from "next/web-vitals";
4+
5+
import * as Sentry from "@sentry/nextjs";
6+
7+
/**
8+
* Next.js Web Vitals를 Sentry로 전송하는 컴포넌트
9+
*
10+
* 측정되는 메트릭:
11+
* - CLS (Cumulative Layout Shift): 레이아웃 안정성
12+
* - FCP (First Contentful Paint): 첫 콘텐츠 표시 시간
13+
* - FID (First Input Delay): 첫 입력 지연 (INP로 대체 예정)
14+
* - INP (Interaction to Next Paint): 상호작용 응답성
15+
* - LCP (Largest Contentful Paint): 최대 콘텐츠 표시 시간
16+
* - TTFB (Time to First Byte): 첫 바이트까지의 시간
17+
*/
18+
export function WebVitals() {
19+
useReportWebVitals((metric) => {
20+
// Web Vitals를 Sentry 트랜잭션으로 전송
21+
const { name, value, rating, navigationType, id } = metric;
22+
23+
// Sentry의 현재 트랜잭션에 measurement 추가 (v7 API)
24+
const transaction = Sentry.getCurrentHub().getScope()?.getTransaction();
25+
if (transaction) {
26+
// CLS는 unitless 메트릭이므로 unit을 생략, 나머지는 millisecond
27+
if (name === "CLS") {
28+
transaction.setMeasurement(name, value);
29+
} else {
30+
transaction.setMeasurement(name, value, "millisecond");
31+
}
32+
transaction.setTag(`${name}.rating`, rating);
33+
transaction.setTag("navigation.type", navigationType);
34+
}
35+
36+
// 각 메트릭을 개별 컨텍스트 키로 저장 (덮어쓰기 방지)
37+
Sentry.getCurrentScope().setContext(`web-vitals.${name}`, {
38+
value,
39+
rating,
40+
navigationType,
41+
id,
42+
});
43+
44+
// 개발 환경에서는 콘솔에도 출력
45+
if (process.env.NODE_ENV === "development") {
46+
console.log(`[Web Vitals] ${name}:`, {
47+
value: name === "CLS" ? value.toFixed(3) : `${Math.round(value)}ms`,
48+
rating,
49+
navigationType,
50+
});
51+
}
52+
});
53+
54+
return null;
55+
}

0 commit comments

Comments
 (0)