회고
[우테코] 12편 : 오픈미션 6일차
| 서론
안녕하세요 팡일입니다!
오늘은 우아한테크코스 8기 프리코스의 오픈미션 6일차 포스팅입니다. 오늘은 진행한 점들에 대해서 기록하고 공유하려고 합니다!
| 오늘 한 일
1) List페이지
실제 본인 회고만 불러오도록 수정
2) Write페이지
userId를 실제 userId로 수정
userId까지 잘 저장되는지 검토
3) Login페이지
로그인 페이지 뼈대 구현
이메일 로그인 기능
구글 로그인 기능
비인증 사용자 접근 제한 예외처리
로그인 시 발생 가능한 Firebase 오류 예외처리
4) Register 페이지
회원가입 페이지 뼈대 구현
이메일 회원가입 기능
구글 로그인 회원가입 기능
5) Firebase
사용자 컬렉션 추가
랜딩페이지 기본 구성 만들어놓기
6) 라우팅 및 구조
라우트 그룹 기능 활용하여 공개/비공개 페이지로 분리
로그아웃 기능 추가
세션 유지 강화
7) UI/UX 개편
랜딩 페이지 도입
전반적으로 카드 기반 레이아웃으로 통일
| 어려웠던 점
1) 비동기로 인한 세션 유지 실패
: Firebase의 인증 상태가 비동기적으로 복원된다는 점이 가장 큰 문제였습니다.
로그인 후 새로고침을 하면 세션이 유지되지 않고 /login으로 리다이렉트되는 현상이 계속 발생했습니다. 원인을 파악해보니, Firebase가 로그인 정보를 localStorage에 저장하긴 하지만 그 복원 과정(onAuthStateChanged)이 비동기적으로 처리되기 때문에 SSR 시점에는 아직 로그인 정보가 준비되지 않았던 것이었습니다.
SvelteKit은 페이지 새로고침 시 서버 렌더링이 먼저 실행되므로, auth.currentUser가 null 상태로 인식되어 “로그인이 안 되어 있다”고 판단해 /login으로 이동시키는 상황이 생긴 것이죠.
// src/routes/(private)/+layout.ts
import { redirect } from '@sveltejs/kit';
import { auth } from '$lib/firebase';
import { onAuthStateChanged } from 'firebase/auth';
export const load = async () => {
if (typeof window === 'undefined') {
return {};
}
const user = await new Promise((resolve) => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
unsubscribe();
resolve(user);
});
});
if (!user) {
throw redirect(307, '/login');
}
return {};
};이 문제는 위의 코드 내의 Promise처럼, 결국 인증 복원 과정이 끝난 뒤에만 리다이렉트를 판단하도록 수정함으로써 해결했습니다.
2) Guard가 작동하지 않아 로그인 없이 접근 가능
: 처음 guard 로직을 추가했음에도 /list 페이지에 로그인 없이 직접 접근이 가능했습니다. 이유를 확인해보니, 모든 페이지가 동일한 +layout.svelte 아래에 존재해 “공개 페이지”와 “로그인 보호 페이지”를 구분할 수 없었던 것이 문제였습니다.
routes
├── (private)
│ ├── +layout.svelte
│ │ ├── +layout.ts
│ │ ├── detail
│ │ │ └── [id]
│ │ │ └── +page.svelte
│ │ ├── list
│ │ │ └── +page.svelte
│ │ └── write
│ │ └── +page.svelte
└── (public)
├── +layout.svelte
├── +page.svelte
├── login
│ └── +page.svelte
└── register
└── +page.svelte그래서 라우트 구조를 (public) 과 (private) 으로 완전히 분리했습니다. 이제 /list, /write, /detail/...은 자동으로 (private) 그룹 아래로 포함되며, Firebase 인증이 없을 경우 guard가 /login으로 리다이렉트하게 되었습니다.
라우팅 구조를 재설계하면서 guard의 역할이 명확해졌고, 의도한 대로 접근 제어가 작동하기 시작했습니다.
3) Firebase 비밀번호 정책 위반 오류 처리
: 회원가입 기능을 구현할 때, Firebase에서 제공하는 createUserWithEmailAndPassword() 함수 호출 시 비밀번호가 너무 짧거나 단순할 경우 auth/weak-password 오류가 발생했습니다.
처음에는 이 에러를 Firebase의 응답 후 처리하려 했지만, UX 측면에서 즉각적인 피드백을 주는 것이 더 낫다고 판단했습니다.
그래서 호출 이전 단계에서 validatePassword() 함수를 통해 비밀번호 유효성을 먼저 검사하고, 문제가 있으면 바로 사용자에게 경고 메시지를 표시하도록 수정했습니다. 이 과정에서 입력 검증 로직의 위치와 타이밍이 얼마나 중요한지 체감할 수 있었습니다.
| 배운 점
1) 비동기 인증 복원의 원리 이해
: Firebase의 세션 복원은 단순히 저장된 토큰을 읽는 게 아니라 내부적으로 onAuthStateChanged를 통해 비동기로 인증 상태를 다시 초기화하는 과정임을 알게 되었습니다. 이에 따라 +layout.ts에서 세션 복원이 완료될 때까지 기다리는 로직을 추가했고, 이후에는 새로고침을 해도 로그인 상태가 안정적으로 유지되었습니다.
2) 라우팅 구조 분리의 중요성
: SvelteKit의 라우트 그룹 개념을 적극적으로 활용한 경험이었습니다. (public)과 (private)을 명확히 나눔으로써 각 페이지에서 인증 여부를 쉽게 구분할 수 있었고, 결과적으로 guard 로직이 훨씬 단순하고 예측 가능하게 작동했습니다. 이후로는 새로운 기능을 추가하더라도, 인증 구조를 건드리지 않아도 될 만큼 구조가 깔끔해졌습니다.
3) 클라이언트 단의 입력 검증 필요성
: Firebase가 내부적으로 정책 검증을 수행하더라도, 사용자 경험(UX) 측면에서는 클라이언트에서 즉시 피드백을 주는 것이 훨씬 자연스럽다는 걸 배웠습니다. 이전에는 서버 응답을 기다렸다가 오류를 처리했지만, 지금은 입력 단계에서 바로 경고를 표시하니 사용자는 문제를 즉시 인지할 수 있고, 불필요한 네트워크 요청도 줄어들었습니다.
| 내일 목표
1) 기타
기능 명세서 수정하기
우리 프로젝트에서 단위 테스트는 어떻게?
모니터링 도구 알아보기
2) Write 페이지
감정 버튼 기능 추가
임시 저장 기능 추가
3) List 페이지
날짜 정렬 기능 추가
타이틀 검색 기능 추가
개별 또는 전체 삭제 기능 추가
4) Update 페이지
기본 UI 구성 조회 기능 추가
수정 기능 추가
5) Detail 페이지
삭제 기능 추가
수정 페이지 이동 기능 추가
6) My 페이지
기본 UI 구성
정보 조회 기능 추가
정보 수정 기능 추가
회원 탈퇴 기능 추가
7) 리팩토링
모듈화 가능한 부분 개선하기
| 한 줄 회고
: "점점 기능이 생기니 좋고, 꾸준히 기록하자"