Skip to content

refactor: 로그인 페이지 성능 개선 (게스트 세션 검사로 인한 버튼 지연 노출) #346

Description

@iOdiO89

리팩토링 내용

루트 진입 → 스플래시 애니메이션 → 로그인 페이지 자동 이동 흐름에서, 로그인 페이지의 게스트/멤버 세션 검사(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.tsxSuspense 경계가 없어, getMe 응답 전까지 로그인 UI 전체가 스트리밍되지 못한다. → 사용자에겐 "게스트 검사가 끝나야 버튼이 뜨는" 것으로 보임.

스플래시 애니메이션(SplashClient.tsx)은 의도된 연출이므로 개선 대상에서 제외한다. 이 이슈는 로그인 페이지 진입 이후의 세션 검사 blocking에만 집중한다.

참고 배경: docs/plan/auth-session-architecture.md — 세션 확보 책임을 proxy로, 권한 검증을 layout으로 분리하는 방향.

개선 방향 (후보)

  • 로그인 버튼 즉시 스트리밍login/page.tsxawait getMeSuspense 경계 안으로 내려, 버튼 UI는 세션 검사와 무관하게 먼저 렌더되게 한다 (login/loading.tsx 또는 컴포넌트 단위 Suspense).
  • 게스트 세션 판정 위치 재검토canReuseGuestSession 계산을 렌더 blocking 경로에서 분리 (클라이언트 지연 계산 또는 버튼 인터랙션 시점으로 이동).
  • layout의 MEMBER 리다이렉트 검사 — proxy 레벨 세션 처리와 중복/blocking 여부 점검 (auth-session-architecture 문서 방향과 정합성 확인).

기대 효과

  • 로그인 페이지 진입 즉시 로그인 버튼 노출 (세션 검사와 렌더 분리)
  • 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

Metadata

Metadata

Assignees

Labels

WEBrefactorExtra attention is needed

Type

Fields

No fields configured for Task.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions