You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
flowchart LR
User["👤 사용자<br/>(Browser)"]
subgraph FE["PinHouse FE · Next.js 15"]
direction TB
MW["middleware.ts<br/>인증 라우트 가드"]
RSC["Server Components<br/>SSR · Prefetch · Hydration"]
CC["Client Components<br/>TanStack Query · Zustand"]
BFF["Route Handlers<br/>app/api/* (BFF)"]
CACHE[("BFF Route Cache<br/>TTL · in-flight dedupe")]
end
subgraph EXT["External"]
API["PinHouse Backend API<br/>(/v1, /v2)"]
OAUTH["Kakao / Naver<br/>OAuth2"]
GPT["OpenAI API"]
CDN["Kakao CDN<br/>(프로필 이미지)"]
end
User -->|HTTPS| MW --> RSC
RSC -->|HTML + dehydrated state| User
User --> CC
CC -->|"/api/* (홈·공고·검색·채팅)"| BFF
CC -->|"axios (마이페이지·온보딩·자격진단 등)"| API
RSC -->|callServer / 자체 BFF 호출| API
BFF <--> CACHE
BFF -->|cookie / Bearer 전달| API
BFF --> GPT
User -->|로그인| OAUTH -->|code| BFF
CC -. 이미지 .-> CDN
Loading
🧱 아키텍처 다이어그램
요청 경계: Browser ↔ BFF ↔ Origin
flowchart TB
subgraph Browser
UI["UI Component"]
Z["Zustand<br/>draft / applied · UI 상태"]
Q["TanStack Query<br/>서버 데이터 캐시"]
URL["URL Search Params<br/>공유 가능한 조건"]
end
subgraph NextServer["Next.js Server"]
Page["page.tsx (RSC)"]
Widget["widgets/*Page<br/>getInitialData → prefetch"]
Route["app/api/* Route Handler"]
subgraph FeatureServer["features/*/server"]
Bff["bff/<br/>캐시 키 · 스코프 · TTL"]
Call["callServer/<br/>Origin fetch · 인증 헤더"]
end
end
Origin[("Backend API")]
UI --> Z
Z -->|applied 조건만| Q
URL --> Page
Page --> Widget --> Call
Q -->|fetch /api/*| Route --> Bff --> Call --> Origin
Q -.->|"axios http (토큰 재발급 인터셉터)"| Origin
Widget -->|dehydrate| Q
Loading
상태 책임 분리
위치
담당
넣지 않는 것
URL / Search Params
공유·북마크·뒤로가기가 필요한 명시 조건
임시 입력값
Zustand
필터 draft/applied, 시트 open, 선택 중 UI 상태
서버 응답 본문
TanStack Query
서버 데이터 조회·캐시·stale·페이지네이션·무효화
UI 표현 상태
BFF (app/api/*)
인증 전달, 조건 검증/정규화, Origin 호출, 서버 캐시
클라이언트 조건의 무검증 통과
axios http
BFF 미전환 도메인의 직접 호출, 401 시 토큰 재발급
조회 캐시 정책
🗂 레이어 구조 (FSD Hybrid)
Next.js App Router(app/) 를 라우팅 전용 레이어로 두고, 실제 구현은 Feature-Sliced Design(src/) 으로 분리했습니다.
flowchart TB
A["app/ · Routing<br/>page.tsx · layout.tsx · api/*"]
W["src/widgets<br/>페이지 단위 조합 · SSR 진입 · Hydration"]
F["src/features<br/>사용자 시나리오 · UI · hooks · model · server(bff/callServer)"]
E["src/entities<br/>도메인 모델 · API · 도메인 hooks"]
S["src/shared<br/>공용 UI · http · endpoints · hooks · lib · types"]
A --> W --> F --> E --> S
features/listings/
├── ui/ # 화면 컴포넌트
├── hooks/ # Query / 상호작용 훅
├── model/ # 검색 조건 타입 · 정규화 · 필터 스토어
└── server/
├── bff/ # BFF 캐시 키 · 스코프 · TTL · 초기 데이터 조회
└── callServer/# Origin API 서버 fetch (cookie / Bearer 전달)
🔄 동작 프로세스
1) 페이지 진입 — SSR Prefetch & Hydration
sequenceDiagram
autonumber
actor U as User
participant MW as middleware
participant P as page.tsx (RSC)
participant W as widgets/*Page
participant CS as callServer
participant API as Backend API
participant C as Client (React Query)
U->>MW: GET /listings?region=...
MW->>MW: access_token / is_auth 쿠키 확인
alt 미인증 & 보호 라우트
MW-->>U: 302 /login
end
MW->>P: next()
P->>P: searchParams → criteria 파싱·정규화
P->>W: initialFilter 전달
W->>CS: getInitialData(filter)
CS->>API: fetch (cookie, Bearer)
API-->>CS: 첫 페이지 데이터
W->>W: queryClient.prefetch(queryKey(criteria))
W-->>U: HTML + dehydrate(state)
U->>C: HydrationBoundary로 캐시 복원
Note over C: 같은 queryKey → 첫 렌더 추가 요청 없음
Loading
2) 클라이언트 조회 — BFF 캐시
sequenceDiagram
autonumber
participant C as Client (useQuery)
participant R as app/api/listings/search
participant B as bff/listingsRouteCache
participant CS as callServer
participant API as Backend API
C->>R: GET /api/listings/search?q=...
R->>R: criteria 파싱 · 정규화
R->>B: resolveScope(cookie) → public | session:hash
R->>B: cacheKey = v1:listing-search:{scope}:{sha1(criteria)}
alt 캐시 HIT (TTL 5분 이내)
B-->>R: cached data
else in-flight 요청 존재
B-->>R: 진행 중 Promise 공유
else MISS
B->>CS: loader()
CS->>API: fetch
API-->>CS: data
CS-->>B: 성공 응답만 저장
end
R-->>C: 200 { data } + x-pinhouse-cache: HIT|MISS|BYPASS
Loading
3) 소셜 로그인
sequenceDiagram
autonumber
actor U as User
participant O as Kakao / Naver
participant CB as /api/auth/callback
participant API as Backend API
U->>O: 소셜 로그인
O-->>CB: redirect ?code=
CB->>API: exchangeOAuthCode(code)
alt 신규 회원 (TOKEN_REQUIRED)
CB-->>U: 302 /signup?state=tempKey
else 기존 회원
CB->>CB: access_token · refresh_token · pinpoint_id<br/>httpOnly 쿠키 설정
CB-->>U: 302 /home
end
Note over U,API: 이후 401 발생 시 axios 인터셉터가 refresh 1회 수행<br/>대기 요청은 큐에서 재시도 · 실패 시 로그아웃
Loading
4) 자격 진단 → 추천
flowchart LR
S1["기본 정보 입력"] --> S2["가구·소득·자산<br/>단계형 설문"] --> R["진단 결과"]
R --> F["최종 결과"]
R --> REC["진단 기반 추천 공고<br/>(무한 스크롤)"]
REC --> D["공고 상세 / 방 비교"]
Axios 인터셉터에서 토큰 재발급 중 요청을 큐잉해 refresh 중복 호출 방지, 재발급 실패 시 로그아웃.
6. 디자인 시스템 & 품질
cva 기반 variants + Radix UI 헤드리스 컴포넌트로 공용 UI 구성, Storybook/Chromatic 으로 시각 검증.
검색 조건 정규화·필터 스토어·BFF 캐시·API 모듈은 Jest 로 테스트.
📈 개발 과정
timeline
title PinHouse FE 개발 타임라인
2025.09 : Next.js 15 + FSD 폴더 구조 세팅
: 이슈 / PR 템플릿 정의
2025.10 : 로그인 폼 · 온보딩 · 미들웨어
: Storybook 도입 · 소셜 로고
2025.11 : 공용 컴포넌트 (Input, SearchBar, Headless UI)
: 공고 검색 · 빠른 탐색
2025.12 : 무한 스크롤 · 방 타입 상세 API + Jest
: 자격 진단 기본 정보 폼
2026.01 : 공고 상세 필터 · 방 비교 · 스켈레톤
: 홈 핀포인트 API · 글로벌 검색 UI
2026.02 : 해상도별 반응형 · 진단 기반 추천 API
: 자격진단 공용 헤더
2026.03 : 홈 SSR / BFF 구조 전환
: 공고 리스트 · 방 비교 BFF prefetch · AI 챗봇 BFF
2026.04 : 채팅 SSR 연동 · 응답 구조화 · CTA
: 홈 bootstrap / 자격진단 BFF 통일
2026.05 : 공고 상세 · 홈 검색 BFF 전환
2026.06 : AI Agent 워크플로 도입 · SSR 라우팅 개선
2026.07 : BFF 캐시 정책 수립 · 사용자 스코프 분리 캐시 리팩터링