리팩토링 내용
루트 진입 → 스플래시 애니메이션 → 로그인 페이지 자동 이동 흐름에서, 로그인 페이지의 게스트/멤버 세션 검사(getMe)가 서버 컴포넌트 렌더를 blocking 하면서 로그인 버튼이 즉시 뜨지 않고 검사가 끝난 뒤에 뜨는 현상을 개선한다.
현상
- 루트(
/) 진입 시 스플래시 애니메이션 재생 후 로그인 페이지로 자동 이동
- 로그인 페이지 진입 후 로그인 버튼들이 바로 안 뜨고 잠시 뒤에 노출됨
- 원인은 로그인 페이지가 게스트 세션 여부를 검사(
getMe)하는 동안 페이지 전체가 렌더 대기 상태에 머물기 때문
라이트하우스 측정 결과
https://dev.piki.day/login 기준 (Moto G Power 에뮬레이션, Slow 4G throttling, Lighthouse 13.3.0, 2026-07-16 측정)
| 지표 |
값 |
상태 |
| Performance |
66 |
개선 필요 |
| First Contentful Paint (FCP) |
1.1s |
양호 |
| Largest Contentful Paint (LCP) |
13.8s |
❌ 매우 나쁨 |
| Total Blocking Time (TBT) |
40ms |
양호 |
| Cumulative Layout Shift (CLS) |
0.024 |
양호 |
| Speed Index (SI) |
10.8s |
❌ 매우 나쁨 |
FCP(1.1s)는 양호한데 LCP(13.8s)·SI(10.8s)가 바닥이라는 점이 핵심이다. 즉 초기 화면은 빠르게 그려지지만, 주요 콘텐츠(로그인 버튼 등)가 실제로 그려지기까지가 극단적으로 늦다. 이는 서버 컴포넌트의 getMe await로 로그인 UI 스트리밍이 지연되는 원인 분석과 직접 맞물린다.
Lighthouse가 제시한 개선 인사이트도 함께 참고한다.
- Render-blocking requests — 약 250ms 절감 여지
- Reduce unused JavaScript — 약 235 KiB
- Minimize main-thread work — 2.8s
- Legacy JavaScript / Minify JavaScript — 약 15 KiB / 23 KiB
- Page prevented back/forward cache restoration — 실패 사유 2건
원인 분석
체감 지연은 클라이언트의 조건부 렌더링이 아니라, 서버 컴포넌트의 await getMe()(세션 검사)가 렌더 파이프라인을 blocking 하기 때문이다.
1) login/layout.tsx — 렌더 파이프라인 최상단 blocking
apps/web/src/app/login/layout.tsx:10-16
const user = await queryClient
.fetchQuery({ queryKey: ['me'], queryFn: getMe })
.catch(() => null);
if (user?.identityType === 'MEMBER') redirect(ROUTES.HOME);
layout은 하위(page/버튼)보다 먼저 완료돼야 하므로, 이 await가 끝날 때까지 이하 전체 렌더가 막힌다.
2) login/page.tsx — 버튼 JSX 생성 전 재차 blocking
apps/web/src/app/login/page.tsx:20-23
const user = await getQueryClient()
.fetchQuery({ queryKey: ['me'], queryFn: getMe })
.catch(() => null);
const canReuseGuestSession = user?.identityType === 'GUEST';
이 await 뒤에서야 LoginButtons를 포함한 JSX가 생성된다(page.tsx:41).
getQueryClient()는 서버에서 요청당 단일 인스턴스 + staleTime: 60s라 layout/page의 fetchQuery(['me'])는 네트워크 호출을 1회 공유한다(중복 호출 아님). 다만 그 1회가 끝날 때까지 버튼 포함 전체 스트리밍이 차단된다.
3) Suspense / loading 경계 부재
login/ 디렉터리에 loading.tsx나 Suspense 경계가 없어, getMe 응답 전까지 로그인 UI 전체가 스트리밍되지 못한다. → 사용자에겐 "게스트 검사가 끝나야 버튼이 뜨는" 것으로 보임.
스플래시 애니메이션(SplashClient.tsx)은 의도된 연출이므로 개선 대상에서 제외한다. 이 이슈는 로그인 페이지 진입 이후의 세션 검사 blocking에만 집중한다.
참고 배경: docs/plan/auth-session-architecture.md — 세션 확보 책임을 proxy로, 권한 검증을 layout으로 분리하는 방향.
개선 방향 (후보)
기대 효과
- 로그인 페이지 진입 즉시 로그인 버튼 노출 (세션 검사와 렌더 분리)
- LCP·SI 개선 — 주요 콘텐츠가 세션 검사 완료를 기다리지 않고 먼저 그려짐 → Performance 점수 상승
- 세션 검사 실패/지연이 UI 노출을 막지 않음
관련 파일
apps/web/src/app/login/layout.tsx
apps/web/src/app/login/page.tsx
apps/web/src/app/login/_components/LoginButtons.tsx
apps/web/src/apis/getMe.ts
apps/web/src/app/login/_hooks/usePostGuestLogin.ts
리팩토링 내용
루트 진입 → 스플래시 애니메이션 → 로그인 페이지 자동 이동 흐름에서, 로그인 페이지의 게스트/멤버 세션 검사(
getMe)가 서버 컴포넌트 렌더를 blocking 하면서 로그인 버튼이 즉시 뜨지 않고 검사가 끝난 뒤에 뜨는 현상을 개선한다.현상
/) 진입 시 스플래시 애니메이션 재생 후 로그인 페이지로 자동 이동getMe)하는 동안 페이지 전체가 렌더 대기 상태에 머물기 때문라이트하우스 측정 결과
https://dev.piki.day/login기준 (Moto G Power 에뮬레이션, Slow 4G throttling, Lighthouse 13.3.0, 2026-07-16 측정)FCP(1.1s)는 양호한데 LCP(13.8s)·SI(10.8s)가 바닥이라는 점이 핵심이다. 즉 초기 화면은 빠르게 그려지지만, 주요 콘텐츠(로그인 버튼 등)가 실제로 그려지기까지가 극단적으로 늦다. 이는 서버 컴포넌트의
getMeawait로 로그인 UI 스트리밍이 지연되는 원인 분석과 직접 맞물린다.Lighthouse가 제시한 개선 인사이트도 함께 참고한다.
원인 분석
체감 지연은 클라이언트의 조건부 렌더링이 아니라, 서버 컴포넌트의
await getMe()(세션 검사)가 렌더 파이프라인을 blocking 하기 때문이다.1)
login/layout.tsx— 렌더 파이프라인 최상단 blockingapps/web/src/app/login/layout.tsx:10-16layout은 하위(page/버튼)보다 먼저 완료돼야 하므로, 이
await가 끝날 때까지 이하 전체 렌더가 막힌다.2)
login/page.tsx— 버튼 JSX 생성 전 재차 blockingapps/web/src/app/login/page.tsx:20-23이
await뒤에서야LoginButtons를 포함한 JSX가 생성된다(page.tsx:41).3) Suspense / loading 경계 부재
login/디렉터리에loading.tsx나Suspense경계가 없어,getMe응답 전까지 로그인 UI 전체가 스트리밍되지 못한다. → 사용자에겐 "게스트 검사가 끝나야 버튼이 뜨는" 것으로 보임.개선 방향 (후보)
login/page.tsx의await getMe를Suspense경계 안으로 내려, 버튼 UI는 세션 검사와 무관하게 먼저 렌더되게 한다 (login/loading.tsx또는 컴포넌트 단위Suspense).canReuseGuestSession계산을 렌더 blocking 경로에서 분리 (클라이언트 지연 계산 또는 버튼 인터랙션 시점으로 이동).기대 효과
관련 파일
apps/web/src/app/login/layout.tsxapps/web/src/app/login/page.tsxapps/web/src/app/login/_components/LoginButtons.tsxapps/web/src/apis/getMe.tsapps/web/src/app/login/_hooks/usePostGuestLogin.ts