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
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ hero:
link: /stack
- theme: brand
text: 📜 트러블슈팅
link: /trouble
link: /trouble/index.md
- theme: brand
text: 🗂️ 프로젝트 폴더구조
link: /folder
Expand Down
79 changes: 79 additions & 0 deletions docs/trouble/gradient.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
### ✔ 문제 요약

### 1. **문제 상황**

- 매칭 성사 화면에서 의도한 **배경 그라디언트**와 **로띠(Lottie) 애니메이션 강조 그라디언트**가 정상적으로 표시되지 않는 문제가 발생했다.
- 기존에는 해당 그라디언트가 정상적으로 렌더링되고 있었으나, **API 연동 작업**과 **리베이스 과정**, 그리고 **전체 레이아웃 구조 수정**을 반복하면서 예상치 못하게 레이아웃이 깨지고, 그라디언트가 전혀 보이지 않는 상황이 발생했다.
- 초기 설계 단계에서 **`position: fixed`를 가급적 사용하지 않으려고 했던 선택**이 이번 문제에 영향을 준 것으로 보인다. 그라디언트를 fixed로 처리하지 않고 상대적인 레이아웃에서 구현하려다 보니, **상위 컨테이너의 overflow, z-index, transform 속성** 등의 영향을 받으면서 의도치 않게 가려지는 현상이 발생했을 가능성이 높았다.
- 특히 **리베이스 후 코드 충돌을 해결하면서 일부 스타일 로직이 누락되거나 덮어씌워졌을 가능성**도 존재했다. 기존에 잘 보이던 그라디언트가 사라진 시점이 명확하지 않아 원인을 추적하는 데 시간이 소요되었다.
- QA 과정에서 Lottie 애니메이션의 강조 영역이 정상적으로 표현되지 않아, 화면의 **강조 포인트가 사라지고 시각적 완성도가 떨어지는 문제**가 드러나면서 해당 이슈가 중요하게 부각되었다.

### ✔ 원인 분석

- 기존 구조에서는 배경 그라디언트와 로띠 강조 그라디언트를**absolute**로 포지셔닝하여,`.matching-success-background`, `.matching-lottie-gradient` 클래스를 통해 배치했다.
- 하지만,**Header**가 상위 Layout 컴포넌트에서 먼저 렌더링되고, 배경 그라디언트가 포함된 MatchingSuccessView는 그 아래 DOM 계층에 위치해 있었다.
- 이때, 아무리 z-index를 높게 주어도 **부모 stacking context**의 영향으로 Header보다 위로 올라오지 않았다.
- **z-index와 position 속성은 DOM 계층 구조의 맥락(=stacking context) 안에서만 동작한다.**
- 부모 요소가 낮은 z-index를 가지거나 stacking context를 새로 만들면, 자식이 아무리 z-index를 높여도**부모의 stacking context를 벗어나지 못하였다.**
- 특히, Header가 상위에 있고, 배경 그라디언트가 상대적으로 하위 DOM에 있으면 z-index만 높여서는 절대 위로 올라오지 않는다.

### ✔ 해결 방법

### **absolute → fixed**

- **absolute** 대신 **fixed** 포지셔닝을 사용하여 해결했다.
- `position: fixed`는 DOM 계층과 무관하게**뷰포트 기준**으로 배치되므로, stacking context의 영향을 받지 않았다.
- 배경 그라디언트와 로띠 강조 그라디언트를 fixed로 선언하여,**Header보다 아래, 주요 콘텐츠보다 뒤**에 자연스럽게 깔리도록 설계하였다.

### **Foreground와 Background의 명확한 분리**

- **텍스트, Lottie 애니메이션 등 주요 콘텐츠**는
z-10 이상으로 두어 foreground 계층에서 명확하게 표현하였다.
- **배경 그라디언트**는 z-0 이하로 두어
background 계층에서 자연스럽게 깔리도록 조정하였다.

### **Layout 및 Route 구조는 그대로**

- 전체 Layout 및 Route 구조는 그대로 두고, **MatchingSuccessView 내부에서만 포지셔닝을 수정**하여 영향 범위를 최소화했다.
- 이 접근법 덕분에 **다른 페이지나 컴포넌트에 미치는 부작용 없이 문제를 해결**할 수 있었다.

### 실제 CSS 예시 (custom-utilities.css)

```css
.matching-success-background {
@apply absolute left-1/2 w-[30rem] h-[30rem] blur-3xl rounded-full z-[var(--z-negative-5)];
top: calc(50% + 10px);
transform: translate(-50%, -50%);
background: radial-gradient(67.17% 67.17% at 33.26% 84.78%, #9be88a 5%, #bcf3ff 88.05%);
}

.matching-lottie-gradient {
@apply absolute left-1/2 top-1/2 w-[18rem] h-[18rem] -translate-x-1/2 -translate-y-1/2 rounded-full z-[var(--z-negative-1)];
background: radial-gradient(
74.24% 74.24% at 50.22% 50.22%,
rgba(255, 255, 255, 0) 46.82%,
#ffffff 72.4%
);
}
```

### 실제 적용 예시 (MatchingSuccessView)

```tsx
<div className="fixed top-[17rem] left-0 w-full h-full z-0">
<div className="matching-success-background" />
<div className="matching-lottie-gradient" />
<div className="z-[var(--z-card-profile-4)] h-[16rem] w-[16rem] flex-row-center">
<Lottie src={LOTTIE_PATH.SUCCESS} loop className="w-[16rem]" />
</div>
</div>
```

- 기존 absolute → fixed로 변경
- z-index는 0 이하로 설정하여 Header보다 뒤에, 주요 콘텐츠보다 아래에 위치

### ✔ 회고 및 개선 방향

- **z-index 문제는 단순히 값의 크기로 해결되지 않는다.** Stacking context에 대한 명확한 이해가 없으면 레이아웃 문제를 해결하기 어렵다는 점을 다시 깨달았다.
- `position: fixed`는 **DOM 구조에 구애받지 않고 원하는 레이어링을 구현할 수 있는 강력한 도구**임을 체감했다.
- 이번 경험으로 인해 **레이아웃을 건드리지 않고, 컴포넌트 단에서 포지셔닝 전략을 유연하게 바꾸는 접근법**이 훨씬 효율적일 수 있다는 것을 배웠다.
Binary file added docs/trouble/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/trouble/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions docs/trouble/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
layout: home

hero:
name: "트러블슈팅 "
tagline: 메잇볼 클라이언트 트러블슈팅 문서를 다루고 있습니다.
actions:
- theme: brand
text: protectedRoute 설정 관련
link: /trouble/protectedRoute
- theme: brand
text: useFunnel 기반 온보딩 구조 분기 및 상태 유지 전략
link: /trouble/useFunnel
- theme: brand
text: React Hooks 규칙, Supense 가이드에 맞는 매칭 카드 구조 개선
link: /trouble/suspense
- theme: brand
text: 사라진 그라디언트를 찾아서..
link: /trouble/gradient

---

79 changes: 79 additions & 0 deletions docs/trouble/protectedRoute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
로그인 하지 않은 사용자가, 로그인 이후 접근 가능한 경로로 접속했을때는 splash 화면으로 이동 후 로그인 페이지로 가는 흐름

### ✔ 도입한 기술

이번 로그인 흐름 개선에서는 React Router 기반의 라우팅 시스템을 구축하면서, `protected route`와 `public route`를 명확히 분리하는 구조를 도입했다. 이를 통해 로그인 상태에 따라 접근 가능한 경로를 엄격히 제어하고자 했다. 인증 상태 확인과 사용자 정보를 효율적으로 관리하기 위해 `tanstack-query`를 활용하여 사용자 상태를 서버에서 조회하는 `useAuth` 커스텀 훅을 만들었고, 인증 흐름 제어는 `AuthGuard` 컴포넌트로 구현했다. 또한, 카카오 소셜 로그인 연동 시 발생하는 인가 코드를 받아 서버에 전달해 로그인 처리를 하는 `LoginCallback` 컴포넌트가 있다. 로그인 과정 중 401 Unauthorized 상태가 발생하면 인터셉터로 이를 감지해 자동으로 로그인 페이지로 리다이렉트하는 로직도 함께 구성되었다.

### ✔ 문제 요약

초기에는 로그인 필요 페이지에 로그인하지 않은 사용자가 접근할 경우, 적절한 보호 및 리다이렉트 처리가 체계적으로 이뤄지지 않아 사용자 경험이 일관되지 않았다. 특히, 카카오 로그인 버튼 클릭 후 콜백에서 토큰 발급 및 사용자 정보 조회 후 화면 전환까지의 흐름이 명확하지 않았고, 인증 상태를 판단하는 `useAuth` 훅과 `AuthGuard`의 역할 분리가 애매해 로직이 복잡하게 얽혀 있었다. 이로 인해 로그인 성공 여부와 회원 가입 여부 판단 후 올바른 화면으로 자연스럽게 이동시키는 부분에서 예외 상황이 발생하거나 불필요한 재렌더링이 많았다. 더불어 interceptor에서 발생하는 401 에러 처리와 인증 흐름이 명확히 연결되지 않아 중복된 인증 검사와 비효율적인 네비게이션이 발생했다.

추가적으로 인증되지 않은 사용자가 홈 같은 인증 필요 경로에 접근할 경우, Tanstack Query를 통한 API 요청이 먼저 발생해 서버로부터 401 Unauthorized 에러가 반환된다. 이때 인증 상태 판단 전에 API 요청이 먼저 실행되고, 인증 실패(Hi developers… .. ㅋㅋ) 후 스플래시 화면으로 리다이렉트되면서 화면 깜빡임 현상이 발생해 사용자 경험이 떨어졌다.

### ✔ 원인 분석

가장 큰 원인은 인증 흐름의 단계별 역할과 경계가 명확히 정의되어 있지 않은 데 있었다. 카카오 로그인 콜백에서 인가 코드를 받아 서버에 전달하고, 서버로부터 로그인 성공 응답을 받은 후 클라이언트가 사용자 정보를 조회해 로그인 상태를 판단하는 부분이 분리되어 있었고, 이 사이의 상태 관리가 일관적이지 않았다. 또한, `useAuth` 훅이 사용자 상태를 조회하지만 이 결과를 기반으로 `AuthGuard`가 경로 접근을 제어하는 역할이 뚜렷하게 구분되지 않아 책임이 분산되었다. interceptor는 401 상태 발생 시 로그아웃 혹은 로그인 페이지 이동을 처리하지만, 그 흐름이 `useAuth`나 라우터와 조화롭게 연결되지 못해 중복 검사와 예외 처리가 반복됐다. 결과적으로 로그인 전, 로그인 후 화면 전환과 상태 반영이 비동기적으로 꼬여 흐름 제어가 어려웠다.까깜

깜빡임 관련 현상은, 라우터에서 인증 보호 기능(`AuthGuard`)이 자식 컴포넌트 렌더링 전에 선행되지 않아, 인증 검증 전 API 요청이 발생하는 문제가 있었다. 또한 라우터에 에러 처리용 `errorElement`가 없어서 401 에러로 인한 예외 상황이 자연스럽게 핸들링되지 않았다. 결과적으로 사용자는 인증 상태 확인 이전에 불필요한 API 호출과 깜빡임 현상을 경험했다.

### ✔ 해결 방법

- 라우터 구조 재설계: React Router의 createBrowserRouter를 활용해, 로그인 필요 경로를 AuthGuard 컴포넌트로 감싸고, 로그인 전용 경로는 publicRoutes로 분리해 두었다. 이로써 인증 여부에 따라 접근 가능한 경로가 엄격히 구분되도록 했다.
- useAuth 훅 개선: 서버에서 사용자 상태를 조회해 닉네임 존재 여부(isAuthenticated)와 가입 조건(isNotMatched) 등을 명확히 반환하도록 하여, 로그인 여부 및 추가 매칭 필요 여부를 판별하는 로직을 일원화했다. 캐시 무효화 기능을 활용해 사용자 상태 갱신도 쉽게 할 수 있도록 설계했다.
- AuthGuard 컴포넌트 역할 명확화: useAuth에서 반환된 로그인 상태와 로딩 상태를 토대로 로그인되지 않은 경우 Navigate 컴포넌트를 이용해 로그인 페이지로 리다이렉트시키고, 인증 중이면 로딩 상태를 보여주도록 하여 접근 제어를 엄격하게 구현했다.
- 깜빡임 관련 해결 방법으로는 , 라우터에서 `AuthGuard`를 최상위 레벨로 배치해, 인증 여부 확인이 먼저 이루어지고 자식 라우트들이 렌더링 되도록 변경했다. 이를 통해 인증이 안 된 경우 자식 컴포넌트들의 API 요청 자체를 막았다. 또, 라우터에 `errorElement`를 빈 컴포넌트 또는 별도 에러 UI로 추가해 예외 상황을 자연스럽게 처리하도록 함으로써, 예기치 않은 에러 발생 시 사용자에게 불필요한 깜빡임을 보여주지 않도록 했다. `AuthGuard` 컴포넌트에서 인증 상태 로딩 중엔 스플래시 화면을 렌더링하고, 인증 실패 시 로그인 페이지로 즉시 리다이렉트하도록 개선해 사용자 경험을 부드럽게 만들었다.

- 코드 일부…

```tsx
// router.tsx
import { createBrowserRouter } from 'react-router-dom';
import AuthGuard from '@routes/auth-guard';
import Layout from '@routes/layout';
import Splash from '@pages/login/components/splash';
import ErrorView from '@pages/error/error-view';
import { protectedRoutes } from '@routes/protected-routes';
import { publicRoutes } from '@routes/public-routes';
import { ROUTES } from '@routes/routes-config';

export const router = createBrowserRouter([
{
path: ROUTES.SPLASH,
element: <Splash />,
},
...publicRoutes,
{
path: '/',
element: <AuthGuard />, // 최상위에 AuthGuard 배치
errorElement: <></>, // 빈 컴포넌트나 별도의 에러 UI로 깜빡임 방지
children: [
{
element: <Layout />,
children: [
...protectedRoutes,
{
path: '/error',
element: <ErrorView />,
},
{
path: '*',
element: (
<ErrorView message={`존재하지 않는 페이지입니다.\nURL을 다시 확인해 주세요.`} />
),
},
],
},
],
},
]);

```


### ✔ 회고 및 개선 방향

이번 로그인 흐름 설계 도전은 protected route와 public route를 명확히 분리하여, 인증 상태에 따른 사용자 경험을 체계적으로 관리하는 데 큰 도움이 되었다. 하지만 카카오 로그인 콜백 이후 인가 코드 처리부터 사용자 상태 조회 및 화면 전환까지 이어지는 비동기 흐름이 복잡하여, interceptor, useAuth, AuthGuard 등 여러 컴포넌트가 인증 흐름의 일부를 분담하는 과정에서 상태 변화가 즉시 반영되지 않아 로직 꼬임과 디버깅 어려움이 있었다.

앞으로는 인증 흐름 내 각 역할별 책임과 상태 변화를 더욱 명확히 구분하는 동시에, 전역 상태 관리 솔루션(Recoil, Zustand 등)을 도입해 인증 상태 변화를 일관성 있게 처리하는 방안을 고민할 예정이다. 또한 네트워크 오류, 토큰 만료 등 다양한 예외 처리 케이스를 세밀하게 테스트하여 사용자 이탈을 방지하는 UX 개선에도 주력할 계획이다. 이를 통해 로그인과 인증 로직을 더욱 견고하고 확장성 있게 다듬는 것이 목표다.

이번 개선 작업을 통해 인증 상태가 완전히 확인된 후에만 protected route 내부 컴포넌트가 렌더링되도록 하여, 불필요한 API 호출과 401 에러로 인한 화면 깜빡임 현상을 원천 차단할 수 있었다. 덕분에 스플래시 화면이 자연스럽게 노출되어 사용자 경험이 크게 향상되었다. 앞으로도 인증과 관련된 비동기 상태 관리는 라우터 수준에서 관리하여 페이지 렌더링 타이밍과 API 호출 흐름을 최대한 일치시키는 방향으로 설계할 계획이다. 또한, 에러 핸들링 UI도 일관성 있게 구성해 사용자 이탈을 최소화하는 데 집중할 예정이다.
Loading