캠핑 인스타 만들기 3편
🏕️ Next.js + Supabase로 캠핑 소셜 앱 만들기: UI 리팩토링과 트러블 슈팅 로그
오늘은 기존의 단순한 캠핑 로그 앱을 인스타그램 스타일의 소셜 플랫폼으로 고도화하는 작업을 진행했다. 단순히 기능만 붙이는 것이 아니라, 사용자 경험(UX)을 고려해 UI를 대대적으로 리팩토링하고, 그 과정에서 마주친 몇 가지 기술적인 문제들을 해결했다.
1. UI/UX 대수술: "랜딩 사이트" 느낌 주기
기존 앱은 기능 위주로 투박하게 구성되어 있었다. 사용자가 앱에 들어왔을 때 "와, 요즘 앱 같다!"라는 느낌을 주기 위해 전반적인 레이아웃을 손봤다.
📱 하단 네비게이션 (Bottom Navigation) 개편
- 기존: 4개 탭 (피드, 검색, 글쓰기, 프로필)
- 변경: 5개 탭 (피드, 검색, 작성, 저장, 프로필)
- 이유: 사용자가 자주 찾는 '저장(찜)한 캠핑장' 메뉴의 접근성을 높이고, 아이콘 배치를 균형 있게 맞췄다. 글쓰기 버튼(
PlusCircle)을 중앙에 배치하여 행동 유도(CTA)를 강조했다.
🛎️ 상단 헤더 통일화
- 페이지마다 제각각이던 모바일 헤더를 제거하고, MainNav 컴포넌트 하나로 통일했다.
- 우측 상단에 알림(Bell) 아이콘을 추가해 향후 알림 기능을 위한 공간을 마련했다.
- Radix UI의
DropdownMenu를 활용해 프로필 메뉴를 깔끔하게 정리했다.
📸 스토리(Stories) 영역 추가
- 인스타그램의 상징인 '스토리' 영역을 피드 최상단에 배치했다.
- 현재는 레이아웃(StoriesPlaceholder)만 잡혀있지만, 가로 스크롤(ScrollArea)을 적용해 나중에 실제 데이터만 연동하면 바로 동작하도록 구현했다.
2. 오늘의 트러블 슈팅 (Troubleshooting) 🔥
기능 구현보다 더 값진 것은 버그를 잡는 과정이었다. 오늘 해결한 주요 이슈 3가지를 정리해 본다.
🐛 이슈 1: Hydration Error "서버와 클라이언트가 달라요"
증상: 새로고침만 하면 콘솔에 빨간색 Hydration Error가 발생했다.
원인: SearchCampsiteDialog 컴포넌트에서 trigger prop으로 버튼(Button)을 넘겨주고 있었는데, Radix UI의 DialogTrigger 내부에서 ID 생성 로직(aria-controls)이 서버와 클라이언트 간에 불일치를 일으켰다.
해결:
triggerprop 대신 **children**을 사용하는 방식(Composition 패턴)으로 변경했다.- 부모 컴포넌트(MainNav)에서
<SearchCampsiteDialog><Button>...</Button></SearchCampsiteDialog>형태로 감싸주니 깔끔하게 해결되었다. 역시 React는 합성이 답이다.
🐛 이슈 2: "저장이... 된 줄 알았는데요?" (Silent Failure)
증상: 캠핑장 찜하기(하트)를 눌렀는데, 버튼 색은 바뀌는데 실제로 '저장됨' 페이지엔 아무것도 없었다. 원인:
- **낙관적 업데이트(Optimistic Update)**의 함정: UI는 즉시 바뀌게 해놓고, 실제 API 요청 결과를 제대로 확인하지 않았다.
- 에러 핸들링 누락: Supabase의
.insert()나.delete()가 실패해도 에러를throw하지 않고 조용히 지나가고 있었다. 해결: API 요청의error객체를 확인해서, 실패 시 에러를 던지고 UI를 롤백(Rollback) 하도록 수정했다.
🐛 이슈 3: Missing Table (PGRST205)
증상: "Could not find the table 'public.bookmarks'" 에러 발생. 원인: 마이그레이션 파일(002_bookmarks.sql)은 만들었지만, 실제 DB에 적용(Apply)하는 것을 깜빡했다. 😅 해결: Supabase SQL Editor에서 테이블 생성 쿼리와 RLS(Row Level Security) 정책을 수동으로 실행해 해결했다.
3. 소셜 기능 완성 (Social Features)
- 좋아요(Like): 게시글 좋아요 기능 구현. 낙관적 업데이트를 적용해 클릭 즉시 반응하도록 했다.
- 댓글(Comment): Sheet(Drawer) UI를 사용해 모바일 친화적인 댓글 창을 구현했다.
- 프로필(Profile): 댓글이나 글 작성 시 프로필이 없으면 자동으로 생성해주는 Self-healing 로직을 추가해 UX 끊김을 방지했다.
🚀 앞으로의 계획
- 검색 기능 고도화 (지역 필터 + 검색어 조합)
- 스토리 기능 실제 데이터 연동
- 알림 센터 구현
오늘의 작업으로 앱의 뼈대는 거의 완성되었다. 이제 "예쁜" 앱을 넘어 "잘 돌아가는" 앱으로 다듬어 나갈 차례다.