서른, 프로그래머 입문하다
- 트러블 슈팅 - 로그아웃 세션 유지 문제 최종 수정: 2025.09.11트러블 슈팅 - 배포환경에서 로그아웃 세션 유지 문제cf. window.location.replace() / router.push() 차이상황개발 환경에서는 로그아웃이 잘 되는데, 배포 환경에서는 로그아웃이 되지 않는 문제가 발생했다. 확인해보니, 로그아웃 버튼을 클릭했음에도 Local storage에 access_token이 유지되고 있음을 확인할 수 있었다. 반면 개발환경에서는 바로 삭제되어 로그아웃이 정상적으로 처리됨을 확인하였다. 또한, 배포 환경에서 로그아웃 버튼을 클릭하면, 아래 코드에서처럼 바로 메인화면으로 이동되는 것까지 확인하였다.// 로그아웃const logoutMutation = useMutation({ mutationFn: async () => { .. 2025.09.11
- 회원가입 - Supabase 이메일 인증 vs JWT 최종 수정: 2025.09.10Supabase의 내장 인증 시스템 vs 직접 JWT 구현1. 기본 아키텍처 차이A. Supabase 이메일 인증 방식// Supabase는 내부적으로 JWT를 사용하지만 추상화되어 있음const { data, error } = await supabase.auth.signUp({ email: 'user@example.com', password: 'password123', options: { emailRedirectTo: `${window.location.origin}/auth/confirm`, }});// 세션 확인 (JWT 토큰이 내부적으로 관리됨)const { data: session } = await supabase.auth.getSession();Clie.. 2025.09.10
- 트러블 슈팅 - 중첩 Link 태그 개선 중첩 Link 오류사진과 같이 카드 컴포넌트 안에, 작성자가 내용을 수정 및 삭제할 수 있는 기능을 구현하려고 코드를 작성했다. 그러나 Link 태그 안에 Link 태그를 작성하니 에러가 발생했다. { ... } {showActions && reflection.is_own && ( )} { ... } 이처럼 Link 안에 Link 태그를 사용하는 것은 HTML 표준상 불가능하다. 그래서 다음과 같은 구조로 해결했다.const handleEdit = (e: React.MouseEvent) => { e.stopPropaga.. 2025.09.07
- CI/CD와 테스트 25년 9월 7일pnpm 패키지에 Next.js에 Jest를 도입하려고 하는데, 계속 parse 에러가 나타나고 있다. 뭐가 문제인지 모르겠다.goals 도메인에서만 단위 테스트, 통합 테스트, E2E 테스트를 작성해뒀는데, pnpm test를 시작하면 parsing에러가 나타난다. 그래서 지금은 다른 도메인 기능구현을 먼저하고 있다. CI/CD 적용현재 프로젝트 진행현황은 다음과 같다.도메인 별로 개발을 진행하고 있는 상황이었다. 예산(feat/budget)과 목표(feat/goals) 도메인을 main 브랜치에 PR을 보내고 병합을 마친 상태였다.이전 프로젝트에서 배포과정에서 경험한 고생을 되풀이 하고 싶지 않았다. 그래서 CI/CD를 공부할 겸 적용해보기로 했다.우선, 개발용 브랜치를 하나 생성했다.. 2025.09.07
- UX 개선 - 소프트 삭제, 낙관적 업데이트(삭제와 복구) 최종 수정: 2025.09.04카테고리 항목이 늘어남에 따른 불편함카테고리 항목이 늘어날수록, 항목이 길어지는 문제가 있다. laptop 환경에서만 해도 이렇게 길어지는데, 모바일 환경에서는 항목이 많아질수록 화면보다 더 길어질 우려도 있다. 이는 사용자 경험을 저하시키는 요인이다. 이에 대해, 태그 형태로 변경하는 게 좋겠다는 생각이 들었다. 또한, 사용하지 않는 카테고리를 삭제 기능을 도입하여 관리할 수 있도록 구현하고자 한다. 그러나, 카테고리를 삭제하면 저장된 데이터가 삭제되는 문제가 존재하므로 '소트프 삭제'를 적용할 생각이다.Q. 소프트 삭제?UI 상에서만 삭제하고 데이터는 보존하는 방법 이를 통해, 사용자 입장에서는 주로 사용하는 카테고리만 보이므로 편의성을 높일 수 있다. 적용한 결과는 다.. 2025.09.04
- 트러블 슈팅 - 제약조건 변경에 따른 프로필 생성 누락 최종 수정: 2025.09.11트러블 슈팅 - 제약조건 변경에 따른 프로필 생성 누락 수입과 지출 각각의 카테고리에 동일한 이름을 등록할 수 없는 문제가 있었다. 그 이유는 제약조건에 있었다. 현재 categories 테이블에 UNIQUE(user_id, name) 제약조건이 있는 상황이다. 우선 이것을 제거하자.-- 기존 제약조건 제거ALTER TABLE categoriesDROP CONSTRAINT IF EXISTS categories_user_id_name_key; 새로운 제약 조건으로 type을 추가하는 것이다. UNIQUE(user_id, name, type)type을 통해 수입에 관한 것인지, 지출에 관한 것인지도 함께 구별함으로써 더 특별한 제약조건을 만들었다.-- 새로운 제약조건 (user.. 2025.09.04
- 트러블 슈팅 - 달력 월말 처리 문제와 date-fns 라이브러리 트러블 슈팅 - '경계조건'과 date-fns 라이브러리트러블달력에 다른 날짜들에 대한 값이 잘 나타나지만, 31일에 대해서는 값이 나타나지 않고 있다. 다른 월에 대해 확인해보니, 모든 월말에 대해 동일한 현상이 나타났다.아마도, 날짜 계산에서 월말 처리 문제일 가능성이 높아보인다. useBudget 훅의 월 범위 계산을 date-fns 라이브러리를 사용하여 해결하고자 한다.why?endOfMonth: 윤년, 30일/31일 등 모든 예외 상황을 자동으로 처리해주기 때문이다.유지보수성: 수동 계산에서 발생할 수 있는 오류(윤년, 월별 일수 차이 등)를 방지일관성: 프로젝트에서 이미 date-fnd 라이브러리를 사용하고 있다.기존 코드// 날짜 필터링if (date) { incomeQuery = inco.. 2025.09.04
- Zod와 React Hook Form 이번 프로젝트에서 Zod와 React Hook Form을 처음으로 사용해봤다. ZodTypeScript 친화적인 런타임 스키마/검증 라이브러리z.object({...})로 입력 스키마를 선언하고 safeParse로 실제 값 검증스키마에서 타입을 추론해 컴파일 타임과 런타임 타입 안전성을 확보클라이언트/서버 양측에서 같은 스키마를 재사용하지 좋다. React Hook Form언컨트롤 입력을 기본으로 하는 경량 폼 라이브러리리렌더 최소화: 대형 폼에서도 탁월다양한 UI 컴포넌트와 쉽게 결합되고, @hookform/resolvers/zod로 스키마 기반 검증을 적용할 수 있다. Zod와 React Hook Form가 유용한 경우복잡한 유효성 검사 규칙많은 필드와 중첩된 객체서버 응답 스키마 검증폼 상태 관리가.. 2025.09.02
- 트러블 슈팅 - 프로필 생성 null과 TanStack Query의 retry 프로필 생성 null과 TanStack Query의 retry결과 및 교훈TanStack Query + 데이터베이스 트리거를 함께 사용할 때는 타이밍 이슈 고려single() 과 maybeSingle()의 차이점 이해retry 설정이 여러 차례 쿼리 요청을 만들 수 있다.결과profiles 쿼리가 1번만 실행된다.TanStack Query 과도한 재시도프로젝트의 모든 페이지에서 로딩 시간이 길어서 네트워크탭을 열어 확인해 보니, profile 쿼리가 중복 실행되고 있었다. 각각은 67ms~ 159ms로 빠르지만, 중복 실행됨으로써 누적으로 인해 지연되는 현상이었다.원인: TanStack Query의 재시도useAuth에서 프로필이 null 일 때, 계속 재시도하면서 지연 현상이 발생하고 있었다.// 기존.. 2025.09.01
- 트러블 슈팅: 데드락 문제 해결 계속 로딩화면만 나타나는 문제가 발생했다. useEffect(() => { const supabase = createClient(); // 모든 상태 초기화 함수들을 병렬로 실행하는 헬퍼 함수 const initializeAllStates = async (user: User | null) => { try { if (user) { // 사용자가 로그인한 경우 - 모든 상태 초기화 await Promise.all([ initializeBookmarks(), initializeParticipation(), initializeLectureBookmarks(), in.. 2025.08.28