안녕하세요.
사내 B2C WebView에서 서브 4탭 하단바와 전역 5탭 하단바를 다루면서, 체감 성능·애니메이션 일관성·초기 로드 부담을 맞추는 작업을 정리했습니다.
| 구분 | 내용 |
|---|---|
| 환경 | Next.js App Router + Flutter WebView |
하단 네비는 두 갈래입니다.
collect-gold 탭 쉘 안에서만 쓰는 전용 하단바 (홈, 친구적립 + coming soon 2개)같은 “하단바”처럼 보여도 라우팅 모델이 달라서 prefetch·애니메이션·gate 처리 전략을 나눴습니다.
탭 구성 (좌 → 우)
홈 · 금 모으기 · MY 금고 · 내역 · 금거래소 (실제 라벨/아이콘은 서비스 스펙 기준)

탭 클릭 시 (같은 쉘 · URL만 변경)
[홈] ──Link──▶ /collect-gold
[친구적립] ──▶ /collect-gold/friend-referral
CollectGoldTabPanelHost
│
┌─────────┴─────────┐
▼ ▼
home 패널 friend 패널
display:block display:none ← 한쪽만 visible (hidden 클래스)
하단 CollectGoldBottomNav 는 리마운트 없음 (레이아웃에 1회만 마운트)

하단바 mount
│
├─① Link prefetch={false} ← 즉시 5탭 동시 로드 ❌
│
├─② idle (requestIdleCallback)
│ 전역: / 와 /collect-gold 만 (현재 탭 제외)
│ 금모으기: /collect-gold, 친구적립 href
│
└─③ onPointerEnter / onTouchStart
손·마우스 올린 탭만 router.prefetch(href)
| 금 모으기 4탭 | 전역 5탭 | |
|---|---|---|
| 라우팅 | 같은 탭 쉘 안 keep-alive (CollectGoldTabPanelHost) — URL만 바뀌고 패널은 hidden 토글 | 탭마다 풀 라우트 전환 (별도 page.tsx 트리) |
| 하단 UI | CollectGoldBottomNav | BottomNavigation + BottomNavGateClient |
| prefetch 부담 | 4탭·같은 쉘 → 상대적으로 가벼움 | 5개 대형 라우트 동시 prefetch 시 초기 부담이 크게 드러남 |
| 입장 애니 scope | collectGold | global |
| 파일 | 역할 |
|---|---|
BottomNavIconEntrance.tsx | Framer Motion scale, scope: global / collectGold |
useBottomNavIconEntranceOrchestration.ts | 입장 1회·탭 전환·재탭 pulse 오케스트레이션 |
bottomNavIconEntrance.const.ts | stagger·duration 상수 |
bottomNavEntranceSession.tsx | Provider (전역 / 금 모으기 분리) |
금 모으기는 CollectGoldBottomNav.tsx + collectGoldBottomNavEntranceSession.storage.ts(sessionStorage 키 분리).
전역은 BottomNavigation.tsx + bottomNavEntranceSession.storage.ts.
두 하단바가 같은 UX를 내면서도, 입장 애니가 서로 간섭하지 않도록 scope와 storage를 나눴습니다.
withViewTransition + router.push 조합이 App Router 클라이언트 전환과 맞지 않아 지연·깜빡임이 났습니다.
또 prefetch를 전 탭에 켜 두면 마운트 시 5개 대형 라우트가 한꺼번에 선로딩됩니다.
Link 기본 네비로 통일 (scroll={false} 유지)router.push 제거브라우저·WebView가 익숙한 네비게이션 경로로 돌려서, “눌렀는데 늦게 바뀌는” 느낌을 줄였습니다.
Link prefetch={false} — 마운트 시 모든 탭 동시 선로딩 방지onPointerEnter / onTouchStart에서 router.prefetch(href)requestIdleCallback (미지원 시 1.5s timeout) 이후 고빈도 탭만 1~2개Set으로 이미 prefetch한 href 제외, 활성 탭은 생략| 파일 | 설명 |
|---|---|
useBottomNavRoutePrefetch.ts | 공통 훅 |
bottomNavPrefetch.config.ts | 전역 idle: /, /collect-gold |
CollectGoldBottomNav.tsx | idle: /collect-gold, 친구적립 href |
BottomNavigation.tsx | 전역 5탭 연동 |
| 네비 | idle prefetch | 호버/터치만 |
|---|---|---|
| 전역 5탭 | 홈, 금 모으기 | MY 금고, 내역, 금거래소 등 |
| 금 모으기 4탭 | 홈, 친구적립 | (coming soon 탭 제외) |
“prefetch를 끄면 느리고, 켜면 첫 화면이 무거운” 딜레마를, idle + 의도 두 단계로 나눠 풀었습니다.
하단바 최적화와 별도로, 탭 쉘은 서버 prefetch로 첫 진입 데이터를 맞춥니다.
| 파일 | 역할 |
|---|---|
collect-gold/(tab-shell)/layout.tsx | 탭 쉘 레이아웃 |
prefetchCollectGoldTabShell.ts | React Query prefetch |
CollectGoldTabPanelHost.tsx | keep-alive 패널 호스트 |
재방문 시에는 이미 마운트된 패널을 hidden으로 토글하므로, 전역 5탭 대비 전환 체감이 가볍습니다.
| 테스트 | 검증 내용 |
|---|---|
useBottomNavRoutePrefetch | intent / 현재 탭 스킵 / idle prefetch |
BottomNavigation | hover 시 router.prefetch |
CollectGoldBottomNav | Link prefetch={false}, scroll={false} |
useBottomNavIconEntranceOrchestration | 입장·재탭 pulse |