Skip to content

Commit cb905ec

Browse files
authored
Merge pull request #292 from manNomi/init/centry
fix: sentry guide
2 parents f2fe4c0 + 1a6c469 commit cb905ec

13 files changed

Lines changed: 2517 additions & 592 deletions

package-lock.json

Lines changed: 2455 additions & 519 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@radix-ui/react-progress": "^1.1.2",
2020
"@radix-ui/react-select": "^2.1.6",
2121
"@react-google-maps/api": "^2.19.2",
22-
"@sentry/nextjs": "^7.107.0",
22+
"@sentry/nextjs": "^10.22.0",
2323
"@stomp/stompjs": "^7.1.1",
2424
"@tanstack/react-query": "^5.84.1",
2525
"@tanstack/react-query-devtools": "^5.84.1",

sentry.client.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import * as Sentry from "@sentry/nextjs";
33
Sentry.init({
44
dsn: process.env.SENTRY_DSN || "",
55
environment: process.env.SENTRY_ENVIRONMENT || "development",
6+
7+
// Performance Monitoring: 모든 트랜잭션 샘플링 (프로덕션에서는 0.1~0.3 권장)
68
tracesSampleRate: 1.0,
79

810
// Replay 샘플링 설정
911
replaysSessionSampleRate: 0.1, // 일반 세션의 10%
1012
replaysOnErrorSampleRate: 1.0, // 에러 발생 시 100%
1113

1214
integrations: [
15+
// Browser Tracing: 페이지 로드 및 네비게이션 성능 측정
1316
Sentry.browserTracingIntegration({
1417
tracePropagationTargets: [
1518
"localhost",
@@ -19,8 +22,10 @@ Sentry.init({
1922
/^https:\/\/(www\.)?solid[\-]?connection\.com/,
2023
],
2124
// Web Vitals 자동 수집 활성화
22-
enableInp: true, // Interaction to Next Paint
25+
enableInp: true, // Interaction to Next Paint (INP) 측정
2326
}),
27+
28+
// Session Replay: 사용자 세션 녹화
2429
Sentry.replayIntegration({
2530
maskAllText: true,
2631
blockAllMedia: true,

src/api/auth/client/useDeleteUserAccount.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useRouter } from "next/navigation";
33
import { AxiosResponse } from "axios";
44

55
import { axiosInstance } from "@/utils/axiosInstance";
6-
import { removeAccessTokenToLS } from "@/utils/localStorageUtils";
76

87
import useAuthStore from "@/lib/zustand/useAuthStore";
98
import { toast } from "@/lib/zustand/useToastStore";
@@ -23,8 +22,8 @@ const useDeleteUserAccount = () => {
2322
router.replace("/");
2423
},
2524
onSuccess: () => {
25+
// Zustand persist가 자동으로 localStorage에서 제거
2626
clearAccessToken();
27-
removeAccessTokenToLS();
2827
queryClient.clear();
2928
},
3029
onError: (error) => {

src/api/auth/client/usePostAppleAuth.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { useRouter } from "next/navigation";
22

33
import { AxiosResponse } from "axios";
44

5-
import { isCookieLoginEnabled } from "@/utils/authUtils";
65
import { publicAxiosInstance } from "@/utils/axiosInstance";
7-
import { saveAccessTokenToLS } from "@/utils/localStorageUtils";
86

97
import useAuthStore from "@/lib/zustand/useAuthStore";
108
import { toast } from "@/lib/zustand/useToastStore";
@@ -43,13 +41,9 @@ const usePostAppleAuth = () => {
4341
const { data } = response;
4442

4543
if (data.isRegistered) {
46-
// 기존 회원일 시 - 토큰 저장하고 홈으로 이동
44+
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
45+
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
4746
useAuthStore.getState().setAccessToken(data.accessToken);
48-
49-
// 로컬스토리지 모드일 때만 리프레시 토큰을 로컬스토리지에 저장
50-
if (!isCookieLoginEnabled()) {
51-
saveAccessTokenToLS(data.accessToken);
52-
}
5347
} else {
5448
// 새로운 회원일 시 - 회원가입 페이지로 이동
5549
router.push(`/sign-up?token=${data.signUpToken}`);

src/api/auth/client/usePostEmailAuth.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { useRouter, useSearchParams } from "next/navigation";
22

33
import { AxiosResponse } from "axios";
44

5-
import { isCookieLoginEnabled } from "@/utils/authUtils";
65
import { publicAxiosInstance } from "@/utils/axiosInstance";
7-
import { saveAccessTokenToLS } from "@/utils/localStorageUtils";
86

97
import useAuthStore from "@/lib/zustand/useAuthStore";
108
import { toast } from "@/lib/zustand/useToastStore";
@@ -33,15 +31,10 @@ const usePostEmailAuth = () => {
3331
onSuccess: (data) => {
3432
const { accessToken } = data.data;
3533

36-
// 액세스 토큰은 항상 Zustand 스토어에 저장
34+
// Zustand persist가 자동으로 localStorage에 저장
35+
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
3736
setAccessToken(accessToken);
3837

39-
// 로컬스토리지 모드일 때만 리프레시 토큰을 로컬스토리지에 저장
40-
// 쿠키 모드일 때는 서버에서 HTTP-only 쿠키로 자동 설정됨
41-
if (!isCookieLoginEnabled() && accessToken) {
42-
saveAccessTokenToLS(accessToken);
43-
}
44-
4538
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
4639
const redirectParam = searchParams.get("redirect");
4740
let safeRedirect = "/"; // 기본값

src/api/auth/client/usePostKakaoAuth.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { useRouter, useSearchParams } from "next/navigation";
22

33
import { AxiosResponse } from "axios";
44

5-
import { isCookieLoginEnabled } from "@/utils/authUtils";
65
import { publicAxiosInstance } from "@/utils/axiosInstance";
7-
import { saveAccessTokenToLS } from "@/utils/localStorageUtils";
86

97
import useAuthStore from "@/lib/zustand/useAuthStore";
108
import { toast } from "@/lib/zustand/useToastStore";
@@ -45,14 +43,10 @@ const usePostKakaoAuth = () => {
4543
const { data } = response;
4644

4745
if (data.isRegistered) {
48-
// 기존 회원일 시 - 토큰 저장하고 홈으로 이동
46+
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
47+
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
4948
setAccessToken(data.accessToken);
5049

51-
// 로컬스토리지 모드일 때만 리프레시 토큰을 로컬스토리지에 저장
52-
if (!isCookieLoginEnabled() && data.accessToken) {
53-
saveAccessTokenToLS(data.accessToken);
54-
}
55-
5650
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
5751
const redirectParam = searchParams.get("redirect");
5852
let safeRedirect = "/"; // 기본값

src/api/auth/client/usePostLogout.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { AxiosResponse } from "axios";
22

33
import { axiosInstance } from "@/utils/axiosInstance";
4-
import { removeAccessTokenToLS } from "@/utils/localStorageUtils";
54

65
import useAuthStore from "@/lib/zustand/useAuthStore";
76
import { useMutation, useQueryClient } from "@tanstack/react-query";
@@ -15,8 +14,8 @@ const usePostLogout = () => {
1514
return useMutation({
1615
mutationFn: postLogout,
1716
onSuccess: () => {
17+
// Zustand persist가 자동으로 localStorage에서 제거
1818
clearAccessToken();
19-
removeAccessTokenToLS();
2019
queryClient.clear();
2120
// 로그아웃 후 홈으로 리다이렉트
2221
window.location.href = "/";

src/app/web-vitals.tsx

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,23 @@
22

33
import { useReportWebVitals } from "next/web-vitals";
44

5-
import * as Sentry from "@sentry/nextjs";
6-
75
/**
8-
* Next.js Web Vitals를 Sentry로 전송하는 컴포넌트
6+
* Next.js Web Vitals를 개발 환경에서 콘솔로 확인하는 컴포넌트
97
*
10-
* 측정되는 메트릭:
8+
* 📊 Sentry로 자동 수집되는 메트릭:
119
* - CLS (Cumulative Layout Shift): 레이아웃 안정성
1210
* - FCP (First Contentful Paint): 첫 콘텐츠 표시 시간
1311
* - FID (First Input Delay): 첫 입력 지연 (INP로 대체 예정)
1412
* - INP (Interaction to Next Paint): 상호작용 응답성
1513
* - LCP (Largest Contentful Paint): 최대 콘텐츠 표시 시간
1614
* - TTFB (Time to First Byte): 첫 바이트까지의 시간
15+
*
16+
* ℹ️ Sentry의 browserTracingIntegration (enableInp: true)이 활성화되어 있어
17+
* Web Vitals가 자동으로 수집됩니다. 수동으로 span을 생성할 필요가 없습니다.
1718
*/
1819
export function WebVitals() {
1920
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-
});
21+
const { name, value, rating, navigationType } = metric;
4322

4423
// 개발 환경에서는 콘솔에도 출력
4524
if (process.env.NODE_ENV === "development") {

src/middleware.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ export function middleware(request: NextRequest) {
1111
// return NextResponse.next();
1212
// }
1313

14-
// 쿠키 기반 인증이 활성화된 경우에만 미들웨어 적용
15-
const isCookieAuthEnabled = process.env.NEXT_PUBLIC_COOKIE_LOGIN_ENABLED === "true";
16-
if (!isCookieAuthEnabled) {
14+
// 서버 사이드 인증 체크가 활성화된 경우에만 미들웨어 적용
15+
// (RefreshToken은 항상 HTTP-only 쿠키로 관리됨)
16+
const isServerSideAuthEnabled = process.env.NEXT_PUBLIC_COOKIE_LOGIN_ENABLED === "true";
17+
if (!isServerSideAuthEnabled) {
1718
return NextResponse.next();
1819
}
1920

21+
// HTTP-only 쿠키의 refreshToken 확인
2022
const refreshToken = request.cookies.get("refreshToken")?.value;
2123

2224
// 정확한 경로 매칭 - startsWith 대신 정확한 경로나 세그먼트 기반 매칭 사용

0 commit comments

Comments
 (0)