Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified gifs/kakao.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion src/app/_components/AuthInitializer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { useEffect } from 'react';

import { useUser } from '@/domain/Auth/hooks/useUser';

/**
Expand All @@ -10,7 +12,12 @@ import { useUser } from '@/domain/Auth/hooks/useUser';
* 이 컴포넌트는 UI를 렌더링하지 않습니다.
*/
export default function AuthInitializer() {
useUser();
const { refetch } = useUser(); // 쿼리 객체를 받아서
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

초기화 로직이 명확해졌습니다.

refetch를 명시적으로 호출하여 앱 로드 시 사용자 데이터를 강제로 가져오는 로직이 명확해졌습니다. 이는 인증 상태 동기화를 보장하는 좋은 접근 방식입니다.

다만 useQuery는 기본적으로 컴포넌트 마운트 시에도 실행되므로, refetch() 호출이 중복 요청을 발생시킬 가능성이 있습니다.

중복 요청 여부를 확인하기 위해 다음 스크립트를 실행하여 useQuery의 기본 설정을 확인해보세요:

Also applies to: 17-20


🏁 Script executed:

#!/bin/bash
# useQuery 기본 동작 확인을 위해 관련 설정들을 찾아봅니다
rg -A 10 -B 5 "useQuery|QueryClient" --type ts --type tsx

Length of output: 87


tsx 파일 타입 필터가 인식되지 않아, 전체 파일을 대상으로 useQueryQueryClient를 다시 검색해주세요.


🏁 Script executed:

#!/bin/bash
# 전체 파일에서 useQuery 및 QueryClient 사용 위치를 확인합니다
rg -n -A5 -B5 "useQuery|QueryClient" .

Length of output: 33839


중복 네트워크 요청 방지 검토 필요

AuthInitializer.tsx에서 useUser 훅을 통해 { refetch }를 분해한 뒤, useEffect로 마운트 시 다시 refetch()를 호출하고 있습니다.
그러나 useUser 내부에는 다음과 같이 useQuery를 사용하고 있어:

  • 컴포넌트 마운트 시 기본적으로 getMe 요청을 자동 실행
  • staleTime 기본값(0)으로 마운트마다 재요청이 보장됨

따라서 useEffect에서 추가로 호출하는 refetch()는 불필요한 중복 요청을 유발할 수 있습니다.

수정 제안:

  • useEffect 내의 refetch() 호출 제거
  • 또는 staleTime/refetchOnMount 옵션을 조정해 의도한 동작만 발생하도록 변경

교정이 필요한 위치:

  • src/app/_components/AuthInitializer.tsx
    • 15번 줄: const { refetch } = useUser();
    • 17–20번 줄: useEffect(() => { refetch() }, [refetch])
🤖 Prompt for AI Agents
In src/app/_components/AuthInitializer.tsx around lines 15 to 20, the useEffect
hook calls refetch() on mount, causing redundant network requests because
useUser's underlying useQuery already fetches data on mount with staleTime 0. To
fix this, remove the useEffect block that calls refetch(), or alternatively
adjust the useUser hook's useQuery options by setting staleTime to a higher
value or refetchOnMount to false to prevent duplicate requests.


useEffect(() => {
// 앱 로드시 강제로 한번 요청 실행
refetch();
}, [refetch]);

return null;
}
3 changes: 2 additions & 1 deletion src/domain/Auth/hooks/useUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export const useUser = () => {
queryKey: ['user', 'me'],
queryFn: getMe,
retry: 1,
staleTime: 1000 * 60 * 5,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
});

// 2. useEffect를 사용하여 쿼리 결과에 따른 부가 작업을 처리합니다.
Expand Down
5 changes: 2 additions & 3 deletions src/domain/Auth/utils/setAuthCookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ export default function setAuthCookies<T>(
sameSite: 'lax',
path: '/',

// maxAge: 60 * 60, //! 테스트를 위해 15초로
maxAge: 10,

maxAge: 60 * 60,
// maxAge: 10,
});

response.cookies.set({
Expand Down
18 changes: 10 additions & 8 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export async function middleware(request: NextRequest) {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${refreshToken}`,
Cookie: `refreshToken=${refreshToken}`,
},
},
);
Expand All @@ -147,14 +148,15 @@ export async function middleware(request: NextRequest) {
signal: AbortSignal.timeout(30000),
});

return setAuthCookies(
new NextResponse(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
}),
tokens,
);
const responseText = await response.text();

const finalResponse = new NextResponse(responseText, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});

return setAuthCookies(finalResponse, tokens);
} else {
console.log('[Middleware] Refresh Token 만료 또는 갱신 실패');
}
Expand Down
2 changes: 1 addition & 1 deletion src/shared/libs/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ export const apiClient = ky.create({
headers: {
'Content-Type': 'application/json',
},

credentials: 'same-origin',
hooks: formatErrorResponseHooks,
});
38 changes: 21 additions & 17 deletions src/shared/libs/formatErrorResponseHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,30 @@ const beforeErrorHook = async (error: HTTPError) => {

if (response.status === 401) {
console.error('클라이언트 측에서 401 에러 감지. 세션이 만료되었습니다.');
const { clearUser } = useRoamReadyStore.getState();
clearUser();
const { user, clearUser } = useRoamReadyStore.getState();

queryClient.invalidateQueries({ queryKey: ['user', 'me'] });
if (user) {
console.error('세션 만료 처리 시작');
clearUser();
queryClient.invalidateQueries({ queryKey: ['user', 'me'] });

const currentPathname = window.location.pathname;
const isCurrentlyOnProtectedPage = protectedPageRoutes.some((route) =>
currentPathname.startsWith(route),
);

if (response.status === 401 && isCurrentlyOnProtectedPage) {
console.error(
'클라이언트 측에서 401 에러 감지. 보호된 페이지에서 세션이 만료되었습니다.',
);
const redirectUrl = new URL(
ROUTES.ACTIVITIES.ROOT,
window.location.origin,
const currentPathname = window.location.pathname;
const isCurrentlyOnProtectedPage = protectedPageRoutes.some((route) =>
currentPathname.startsWith(route),
);
redirectUrl.searchParams.set('error', ERROR_CODES.SESSION_EXPIRED);
window.location.href = redirectUrl.toString();

if (isCurrentlyOnProtectedPage) {
const redirectUrl = new URL(
ROUTES.ACTIVITIES.ROOT,
window.location.origin,
);
redirectUrl.searchParams.set('error', ERROR_CODES.SESSION_EXPIRED);
window.location.href = redirectUrl.toString();
} else {
window.location.reload();
}
} else {
console.log('[401] 로그인 안된 상태에서 발생한 요청이므로 무시');
}
}

Expand Down