Step 3 Post 도메인 테크스펙 문서 - 100-hours-a-week/16-team-katopia-fe GitHub Wiki

Post 도메인 테크스펙(Post)

배경(Background)

프로젝트 목표

Post 도메인은 사용자가 자신의 코디/착용 경험을 게시글로 생성하고, 다른 사용자의 게시글을 피드 형태로 소비하며 상호작용(좋아요, 북마크) 할 수 있도록 하는 본 서비스의 핵심 콘텐츠 도메인이다.

본 도메인의 목표는 다음과 같다.

  • 게시글 생성 경험의 진입 장벽 최소화
    • 이미지 업로드, 텍스트 입력, 태그 설정 등 복합 입력을 단일 플로우로 제공
    • 실패 시 입력값을 보존하여 재시도 가능하게 설계
  • 콘텐츠 소비 흐름의 안정성 확보
    • 이미지 중심 피드에서 스크롤, 렌더링, 상호작용이 끊기지 않도록 최적화
  • 명확한 상호작용 모델 제공
    • 좋아요 / 북마크 상태를 즉시 반영하여 사용자의 액션 피드백 강화

핵심 결과(Key Results)

  • KR1 : 게시글 작성 플로우 진입 → 업로드 완료까지 평균 소요 시간 최소화
  • KR2: 게시글 작성 실패 시 입력 값 유지율 100%
  • KR3: 피드 스크롤 중 이미지 로딩 실패/깜빡임 발생률 0건
  • KR4: 좋아요/북마크 클릭 후 UI 반영 지연 체감 0
  • KR5: 게시글 수정/삭제 후 피드 상태 일관성 유지(캐시 불일치 0건)

문제 정의(Problems)

  1. 콘텐츠 생성의 복잡성

    • 게시글 작성은 다음 입력을 동시에 요구함.
      • 이미지 업로드(복수)
      • 텍스트 입력
      • 태그

    → 이 중 하나라도 실패하면, 사용자는 다시 처음부터 작성해야 하는 경우가 많음.

    • 입력 실패/네트워크 오류 시 작성 중 데이터 손실 가능성이 큼.
  2. 이미지 중심 피드는 성능 부담

    • Post 도메인의 피드는 이미지 비중이 매우 높음.
    • 스크롤 중 지속적으로 리렌더링 발생
    • 최적화 없이 구현 시 스크롤 지연, 이미지 깜빡임, 메모리 사용량 증가하게 된다.
  3. 상호작용 상태 동기화 문제

    • 좋아요 / 북마크는 서버 상태 반영, 동시에 여러 화면에 반영되어야 함.
    • 캐시 전략이 명확하지 않으면 클릭했는데 상태가 안바뀌는 UX가 될 수 있고, 화면마다 다른 상태 노출 가능성이 높아짐.
  4. 수정/삭제 이후 상태 불일치

    • 게시글 수정/삭제 이후 피드, 상세 페이지, 마이페이지에서 동일한 게시글 상태가 유지되어야 함.
    • 단일 화면만 갱신되면 캐시 불일치, “삭제 했는데 남아있는 게시글” 같은 치명적 UX 발생.

목표가 아닌 것(Non-goals)

이번 프로젝트에서 다루지 않은 내용:

  • 게시글 추천 및 랭킹 알고리즘 : 좋아요 수 기반 인기 게시글, 개인화 추천 피드, 랭킹 정렬 로직은 본 도메인 범위에 포함하지 않으며 서버 정책 및 데이터 분석을 포함한 확장 과제로 분리한다.
  • 고급 미디어 처리 기능 : 이미지 편집(크롭, 필터, 보정), 동영상 업로드 및 재생, AI 기반 자동 태깅 기능은 지원하지 않으며 기본 이미지 업로드 및 렌더링 까지만 제공한다.
  • 게시글 공개 범위 및 접근 제어 : 게시글 공개/비공개, 친구 공개, 차단 사용자 제외 노출 등 접근 제어 정책은 Auth/Privacy 설계와 밀접하게 연관되어 있어 후속 과제로 분리한다.
  • 게시글 수정 이력 및 복구 가능 : 게시글 수정 히스토리 관리, 삭제 게시글 복구 기능은 데이터 보존 정책과 연계된 기능으로 이번 범위에서는 다루지 않는다.

설계 및 기술자료(Architecture and Technical Documentation)

아키텍처 개요 (Architecture Overview)

  • 프레임워크/라이브러리 : React(v19) + Next.js(v16, App Router)

    • 게시글 피드, 상세, 작성/수정 페이지를 파일 기반 라우팅으로 명확히 분리
    • 이미지 중심 콘텐츠와 사용자 인터랙션이 많은 특성을 고려하여 CSR 중심으로 설계
  • 상태 관리(State Management): Zustand

    • 게시글 작성/ 수정 중 임시 입력 데이터 관리

    • 좋아요/ 북마크 UI 상태 관리

    • 모달, 액션 시트 등 Post 도메인 전역 UI 상태 관리

      → Redux 대비 보일러플레이트가 적고, ContextAPI 대비 렌더링 범위 제어가 용이

  • UI 컴포넌트: Post 도메인 전용 로컬 컴포넌트 개발

    • 공통 : Button, Icon 등
    • 로컬 : PostCard, PostActionBar, ImageGrid, LikeButton, BookmarkButton 등

    → 공통 UI 일관성과 도메인 책임 분리 동시 확보

  • 스타일링 : TailwindCSS

    • 이미지 비율 유지, 그리드 / 리스트 레이아웃 대응 용이
    • 상태 기반 스타일링을 클래스 조합으로 명확히 표현
    • 모바일 퍼스트 레이아웃 및 반응형 대응을 단순하게 유지
  • 폼 관리 : React Hook Form

    • 게시글 작성/수정 폼 관리
    • 이미지 업로드 + 텍스트 입력 + 태그 입력 등 복합 폼 처리
    • 제출 실패 시 입력값 유지
  • 데이터 패칭 : Tanstack Query

    • 게시글 목록(피드) : useQuery / useInfiniteQuery
    • 게시글 상세 조회 : useQuery
    • 게시글 작성 / 수정/ 삭제 : useMutation
    • 좋아요 / 북마크 : Optimistic Update 적용, 서버 응답 실패 시 롤백 처리, 관련 게시글 캐시 동기화
  • SSR/ISR/CSR 적용 범위

    • /post(게시글 피드) : 사용자 인터랙션 및 이미지 스크롤 중심→ CSR
    • /post/{postId}(게시글 상세) : 기본 CSR, 공유 / SEO 요구 발생 시 SSR 적용 가능
    • /post/create, /post/edit/{postId} (게시글 작성/수정) : 사용자 중심 페이지 → CSR

주요 페이지 / 컴포넌트 구조

페이지(Pages)


  • /post : 게시글 피드 페이지(Home Feed)
    • 주요 기능
      • 전체 게시글 피드 조회
      • 게시글 카드 단위 렌더링
      • 게시글 좋아요 / 북마크
      • 게시글 클릭 시 상세 페이지 진입
      • 무한 스크롤 기반 추가 로딩
    • 사용 컴포넌트
      • PostFeedLayout
      • PostList
      • PostCard
      • PostImageCarousel
      • PostActionBar
        • LikeButton
        • BookmarkButton
      • PostSkeleton
    • 데이터 로딩 시점
      • 페이지 최초 진입 시
        • GET /api/posts?size=…
      • 스크롤 하단 도달 시
        • nextCursor 기반 추가 요청
    • 라우팅 : /post → 게시글 피드 유지, 게시글 클릭 → /post/{postId}
  • /post/{postId} : 게시글 상세 페이지
    • 주요 기능
      • 게시글 상세 정보 조회
      • 게시글 이미지 전체 보기
      • 좋아요 / 북마크
      • 게시글 수정 / 삭제(작성자 본인)
      • 댓글 영역 진입
    • 사용 컴포넌트
      • PostDetailLayout
      • PostHeader
      • PostImageViewer
      • PostContent
      • PostActionBar
        • LikeButton
        • BookmarkButton
      • PostMoreActionSheet
      • PostDeleteConfirmModal
    • 데이터 로딩 시점
      • 페이지 진입 시 → GET /api/posts/{id}
    • 라우팅 : /post/{postId} , 수정 클릭 → /post/edit/{postId} , 삭제 완료 → 이전 페이지(/post)로 이동
  • /post/create : 게시글 작성 페이지
    • 주요 기능
      • 이미지 업로드(1장 이상)
      • 게시글 내용 입력
      • 작성 취소 / 작성 완료
      • 작성 중 이탈 방지(confirm modal)
    • 사용 컴포넌트
      • PostFormLayout
      • PostFormHeader
      • PostImageUploader
      • PostContentInput
      • PostSubmitButton
      • PostCancelConfirmModal
    • 데이터 로딩 시점 : 서버 데이터 사전 로딩 없음 , 모든 입력은 클라이언트 상태 기반
    • 라우팅 : /post/create , 작성 완료 → /post/{postId} , 취소 → 이전 페이지
  • /post/edit/{postId} : 게시글 수정 페이지
    • 주요 기능
      • 기존 게시글 데이터 프리필
      • 내용 수정
      • 수정 취소 / 완료
      • 변경 사항 미저장 상태 이탈 방지
    • 사용 컴포넌트
      • PostFormLayout
      • PostFormHeader
      • PostImageUploader
      • PostContentInput
      • PostSubmitButton
      • PostCancelConfirmModal
    • 데이터 로딩 시점
      • 페이지 진입 시 → GET /api/posts/{postId}
    • 라우팅 : /post/edit/{postId} , 수정 완료 → /post/{postId}

주요 컴포넌트(Components)

  • PostFeedLayout

    개요(Overview)

    • 역할 : 게시글 피드 페이지의 전체 레이아웃을 담당하는 컨테이너 컴포넌트
      • 상단 헤더, 하단 네비게이션을 포함한 피드 전용 화면 구조를 제공한다.
    • 책임
      • 피드 영역 스크롤 컨테이너 제공
      • Safe Area 및 하단 네비게이션 영역 고려한 레이아웃 유지
      • 피드 내부 컴포넌트를 감싸는 역할만 수행
    • 설계 의도
      • 페이지 레이아웃과 데이터/리스트 로직을 분리하여 레이아웃 변경 시 피드 로직에 영향이 없도록 설계
    • 스타일링
      • 모바일 기준 full-height 레이아웃
      • 하단 네비게이션 영역 padding 확보
  • PostList

    개요(Overview)

    • 역할 : 게시글 피드에서 게시글 목록을 순차적으로 렌더링하는 리스트 컴포넌트

    • 책임

      • 게시글 데이터 배열을 받아 PostCard 단위로 렌더링
      • 무한 스크롤 트리거 영역 제공
      • 로딩/추가 로딩 상태에 따른 Skeleton 표시
    • props & Interface

      interface PostListProps {
        posts: PostSummary[];
        isLoading: boolean;
        isFetchingNextPage: boolean;
        onReachEnd: () => void; // infinite scroll trigger
      }
      
      
    • 에러 처리 / 엣지 케이스

      • posts가 비어있을 경우 EmptyState 렌더링
      • 최초 로딩중에는 PostSkeleton 표시
  • PostCard

    개요(Overview)

    • 역할 : 게시글 피드에서 게시글 1개를 카드 단위로 표현하는 핵심 컴포넌트

    • 책임

      • 게시글 작성자 정보 표시
      • 게시글 이미지 미리보기
      • 좋아요 / 북마크 UI 제공
      • 카드 클릭 시 상세 페이지 진입 트리거 제공
    • props & Interface

      interface PostCardProps {
        postId: number;
        author: {
          memberId: number;
          nickname: string;
          profileImageUrl?: string | null;
        };
        imageUrls: string[];
        content: string;
        likeCount: number;
        liked: boolean;
        bookmarked: boolean;
      
        onClick: () => void;
        onToggleLike: () => void;
        onToggleBookmark: () => void;
      }
      
      
    • 에러 처리 / 엣지 케이스

      • 이미지 로딩 실패 시 fallback 이미지 노출
      • content가 길 경우 line-clamp 처리
  • PostImageCarousel

    개요(Overview)

    • 역할 : 게시글 카드 내부에서 여러 이미지를 슬라이드 형태로 보여주는 컴포넌트

    • 책임

      • 이미지 배열을 순서대로 렌더링
      • 좌우 스와이프 / 페이지 인디케이터 제공
    • props & Interface

      interface PostImageCarouselProps {
        imageUrls: string[];
      }
      
    • 에러 처리 / 엣지 케이스

      • 이미지 배열이 비어있을 경우 placeholder 렌더링
  • PostActionBar : 게시글 카드 하단의 공통 액션 영역을 담당

  • LikeButton : 게시글 좋아요 토글 버튼

  • BookmarkButton : 게시글 북마크 토글 버튼

  • PostSkeleton : 게시글 피드 로딩 중 표시되는 Skeleton UI

  • PostDetailLayout: 게시글 상세 페이지의 전체 레이아웃 담당

  • PostHeader : 게시글 상단의 메타 정보 및 액션 진입 영역을 담당하는 헤더 컴포넌트

  • PostContent : 게시글 본문 텍스트 영역을 담당

  • PostMoreActionSheet

    개요(Overview)

    • 역할 : 게시글 우측 상단 “⋯” 클릭 시 노출되는 액션 시트

    • 책임

      • 작성자 본인 여부에 따라 액션 분기
        • 본인 : 수정 / 삭제
    • props & Interface

      interface PostMoreActionSheetProps {
        isOpen: boolean;
        isOwner: boolean;
      
        onEdit: () => void;
        onDelete: () => void;
        onClose: () => void;
      }
      
      
    • 에러 처리 / 엣지 케이스

      • isOwner가 false인 경우
        • 수정 / 삭제 버튼 미노출
  • PostDeleteConfirmModal

    개요(Overview)

    • 역할 : 게시글 삭제 전 최종 확인을 위한 모달 컴포넌트

    • 책임

      • 삭제 여부 재확인
      • 실수로 인한 데이터 손실 방지
    • props & Interface

      interface PostDeleteConfirmModalProps {
        isOpen: boolean;
        isLoading?: boolean;
      
        onConfirm: () => void;
        onCancel: () => void;
      }
      
      
    • 에러 처리 / 엣지 케이스

      • 삭제 API 실패 시
        • 모달 유지
        • 토스트 메시지 표시
  • PostFormLayout : 게시글 작성 페이지의 전체 레이아웃을 담당하는 컨테이너 컴포넌트

  • PostFormHeader : 게시글 작성 페이지 상단의 네비게이션 및 액션 영역

  • PostImageUploader

    개요(Overview)

    • 역할 : 게시글에 첨부할 이미지를 업로드하고 미리보기를 제공하는 컴포넌트

    • 책임

      • 이미지 파일 선택
      • 선택된 이미지 미리보기 렌더링
      • 이미지 삭제
    • props & Interface

      interface PostImageUploaderProps {
        images: File[];
        onAddImages: (files: File[]) => void;
        onRemoveImage: (index: number) => void;
      }
      
      
    • 에러 처리 / 엣지 케이스

      • 이미지 0장인 경우
        • “이미지를 1장 이상 추가해주세요.” 헬퍼텍스트 노출
      • 허용되지 않은 파일 타입 / 용량 초과시
        • 에러 헬퍼 텍스트 노출
  • PostContentInput : 게시글 텍스트 내용을 입력받는 컴포넌트

  • PostSubmitButton : 게시글 작성 완료를 트리거하는 하단 고정 버튼

  • PostCancelConfirmModal : 작성 중 페이지 이탈 시 노출되는 확인 모달

    • 책임

      • 작성중인 내용이 사라질 수 있음을 명확히 고지
      • 취소 / 계속 작성 선택 제공
    • props / Interface

      interface PostCancelConfirmModalProps {
        isOpen: boolean;
        onConfirmLeave: () => void;
        onCancelLeave: () => void;
      }
      
      

상태 관리 전략 (State Management Strategy)

1. Global State (Zustand Stores)

  • postFormStore: 게시글 작성/수정 중 발생하는 임시 입력 상태 관리

    • 초기화 시점:

      • 작성 페이지 혹은 수정 페이지 진입 시 resetForm()을 통해 초기화
      • API 제출 성공 후 /post/detail로 이동하기 직전 초기화하여 잔여 데이터 삭제.
    • 주요 액션:

      • setImages(files) / removeImage(index) : 이미지 업로드 및 삭제
      • setContent(text) : 본문 텍스트 업데이트( 자동으로 isDirty : true 처리 )
      • startSubmit() / finishSubmit() : 제출 프로세스 시작 및 종료 제어
      • resetForm() : 전체 상태 초기값으로 리셋
    • Store 구조

      interface PostFormState {
        images: File[];
        content: string;
        isDirty: boolean;
        isSubmitting: boolean;
        // Actions
        setImages: (files: File[]) => void;
        removeImage: (index: number) => void;
        setContent: (value: string) => void;
        startSubmit: () => void;
        finishSubmit: () => void;
        resetForm: () => void;
      }
      
  • postUIStore : Post 도메인 내 공통 UI요소(모달, 액션 시트 등) 제어

    • 초기화 시점:

      • 각 모달의 close 액션 호출 시 관련 상태 초기화.
      • 페이지를 완전히 이탈할 경우 전체 초기화
    • 주요 액션:

      • openMoreAction(postId) : 특정 게시글의 더보기 메뉴(수정/삭제) 노출
      • openDeleteModal(postId) : 특정 게시글 삭제 확인창 노출
      • closeAll() : 모든 UI 요소를 닫고 activePostIdnull로 초기화
    • Store 구조:

      interface PostUIState {
        activePostId?: number; // 현재 제어 대상이 되는 게시글 ID
        isDeleteModalOpen: boolean;
        isMoreActionOpen: boolean;
        // Actions
        openDeleteModal: (postId: number) => void;
        openMoreAction: (postId: number) => void;
        closeAll: () => void;
      }
      
    • 상태 관리 경계:

      • Tanstack Query : 서버로부터 받아오는 게시글 목록, 좋아요 상태, 북마크 데이터 등 서버 기준의 데이터를 관리하며, Optimistic Update를 통해 즉각적인 UI 반응을 구현한다.
      • Zustand : 사용자가 서버에 전송하기 전의 임시 입력값이나 컴포넌트 트리 깊숙한 곳에서 전역으로 띄워야 하는 UI 상태에 집중한다.
  • Local State (React useState, useReducer)

    • PostContentInput: 입력창 포커스 여부, 실시간 글자 수 카운트 표시
    • PostImageUploader: 이미지 업로드 중 로딩 상태, 드래그 앤 드롭 활성화 여부, 브라우저 메모리용 미리보기(Preview) URL
    • PostCard: 마우스 호버(Hover) 상태에 따른 특정 버튼 노출 여부
  • Server Cache State (Tanstack Query)

서버가 진실의 근원(Source of Truth)인 데이터는 캐싱 라이브러리를 통해 관리하여 자동 갱신 및 데이터 무결성을 유지한다. ex) 메인 피드의 게시글 리스트 데이터, 상세 페이지의 댓글 목록, 사용자의 ‘좋아요’ 및 ‘북마크’ 여부 서버 동기화


API 연동 (API Integration)

  • 호출할 백엔드 API 목록:

    • GET /api/posts?size={size}&after={after}: 게시글 목록 조회 (커서 기반 페이징)
    • GET /api/posts/{id}: 게시글 상세 조회
    • PATCH /api/posts/{id}: 게시글 수정 (내용, 이미지, 태그 수정)
    • DELETE /api/posts/{id}: 게시글 삭제
    • POST /api/posts/{id}/likes: 게시글 좋아요 등록
    • DELETE /api/posts/{id}/likes: 게시글 좋아요 해제
    • POST /api/posts/{id}/bookmarks: 게시글 북마크 등록
    • DELETE /api/posts/{id}/bookmarks: 게시글 북마크 해제
  • API 호출 처리

    • Post 도메인 API 호출은 프로젝트 컨벤션에 따라 axios를 사용한다.
    • 서버 상태 관리는 React Query를 활용하여 중복 호출 방지, 캐시 관리, 자동 재요청을 처리한다.
    • API 요청 상태에 따라 Skeleton UI 또는 Spinner를 노출하며 오류 발생 시 토스트 메시지로 사용자에게 안내한다.

    • GET /api/posts?size={size}&after={after}: 게시글 목록 조회 (커서 기반 페이징)
      • 호출 시점
        • 홈 피드 페이지 진입 시
        • 스크롤 하단 도달 시(무한 스크롤)
      • 처리 방식
        • useInfiniteQuery 를 통해 호출
      • 캐시 / 상태 반영
        • 응답 데이터는 React Query 캐시에 페이지 단위로 저장
        • 마지막 게시글 ID를 기준으로 다음 페이지 요청
        • 게시글 수정/삭제 발생 시 invalidateQueries([’posts’])로 목록 캐시 갱신
    • GET /api/posts/{id}: 게시글 상세 조회
      • 호출 시점
        • 게시글 상세 페이지 진입 시
      • 처리 방식
        • useQuery를 통해 호출
      • 캐시/상태 반영
        • 조회된 게시글 데이터는 [’post’, id] 키로 캐싱
        • 좋아요/북마크 변경 시 해당 쿼리만 선택적으로 갱신
    • PATCH /api/posts/{id}: 게시글 수정 (내용, 이미지, 태그 수정)
      • 호출 시점
        • 게시글 수정 페이지에서 저장 버튼 클릭 시
      • 처리 방식
        • useMutation 을 통해 호출
      • 캐시/상태 반영
        • 성공 시 invalidateQueries([’post’, id])
        • 목록 화면이 존재하는 경우 invalidateQueries([’posts’])를 함께 호출하여 데이터 동기화
    • DELETE /api/posts/{id}: 게시글 삭제
      • 호출 시점
        • 삭제 모달에서 삭제 버튼을 클릭 시
      • 처리 방식
        • useMutation 을 통해 호출
      • 캐시/상태 반영
        • 성공 시 invalidateQueries([’posts’])
        • 현재 상세 페이지에서 목록 페이지로 이동
    • POST /api/posts/{id}/likes: 게시글 좋아요 등록
      • 호출 시점
        • 좋아요 버튼 클릭 시
      • 처리 방식
        • useMutation + Optimistic UI 적용
      • 캐시 / 상태 반영
        • 요청 직후 좋아요 상태 및 카운트 로컬 반영
        • 실패 시 이전 상태로 rollback
        • 성공 시 invalidateQueries(['post', id])
    • DELETE /api/posts/{id}/likes: 게시글 좋아요 해제
      • 호출 시점
        • 좋아요 활성 상태에서 버튼 클릭 시
      • 처리 방식
        • useMutation + Optimistic UI 적용
      • 캐시/상태 반영
        • POST와 동일한 방식으로 optimistic update 및 캐시 갱신
    • POST /api/posts/{id}/bookmarks: 게시글 북마크 등록
      • 호출 시점
        • 북마크 버튼 클릭 시
      • 처리 방식
        • useMutation 사용
      • 캐시/상태 반영
        • 성공 시 invalidateQueries(['post', id])
        • 필요 시 북마크 목록 쿼리도 함께 갱신
    • DELETE /api/posts/{id}/bookmarks: 게시글 북마크 해제
      • 호출 시점
        • 북마크 해제 버튼 클릭 시
      • 처리 방식
        • useMutation 사용
      • 캐시/상태 반영
        • 성공 시 관련 캐시 무효화 및 UI 상태 동기화

라우팅 (Routing)

  • Next.js App Router 기반 File-based Routing 사용:
    • 사용자 행동 흐름과 URL 구조를 1:1로 매핑하여 직관적인 탐색 구조 설계
    • 주요 라우트 구조:
      • /post : 게시글 피드(무한 스크롤 탐색)
      • /post/{postId} : 게시글 상세(조회 및 인터랙션)
      • /post/create : 게시글 작성(이미지 업로드 + 내용 입력)
      • /post/edit/{postId} : 게시글 수정(기존 데이터 프리필)
    • Client-side Navigation 적극 활용:
      • 피드와 상세 페이지 간 이동 시 전체 리로드 없이 상태를 유지하여 스크롤 위치 및 캐시 보존
      • 이동 정책 : 작성/수정 성공 시 /post/{postId}로, 삭제 성공 시 /post로 이동.
    • 인가 및 접근 제어:
      • 로그인 필수 페이지: /post/create, /post/edit/{postId}는 비로그인 사용자 접근 시 즉시 차단 및 로그인 유도(바텀시트 또는 /home 리다이렉트).
      • 권한 검증: 수정/삭제는 작성자 본인만 가능하도록 프론트 UI 차단 및 서버측 최종 검증 병행.

폼 처리 및 유효성 검증(Forms & Validation)

  • React Hook Form을 사용한 복합 폼 관리
    • 이미지와 텍스트가 결합된 복합 폼의 상태 관리를 위해 각 페이지 최상단에서 useForm() 호출.
    • PostContentInput, PostImageUploader 등 입력부 컴포넌트를 분리하여 useFormContext 또는 Controller를 통해 응집도 높은 구조 설계.
  • 유효성 검증 로직(Client-side)
    • Zod 스키마 연동 : resolver를 활용해 선언적 검증 로직 적용.
      • 내용 : 필수 입력 및 최대 글자 수 제한.
      • 이미지 : 최소 1장 이상 필수, 허용되지 않는 파일 형식 및 크기 제한.
    • 검증 시점 : onChange(입력 중), onBlur(포커스 아웃), handleSubmit(제출 시) 등 요구사항에 맞춰 유연하게 적용.
  • 서버 측(백엔드) 검증 대응:
    • 서버 응답 에러(4xx) 발생 시(예: 이미지 누락, 권한 없음), 해당 에러를 폼 필드 또는 토스트 메시지로 노출.
    • 실패 복구 UX: 제출 실패 시에도 사용자가 입력한 데이터는 유지하며, isSubmitting 상태를 해제하여 즉시 재시도 가능하도록 처리.
  • 이탈 방지 처리 (Unsaved Changes):
    • 폼이 수정된 상태(isDirty === true)에서 페이지 이탈 시 Confirm 모달을 노출하여 사용자 실수로 인한 데이터 유실 방지.

용어 정의 (Glossary)

  • line-clamp: CSS 속성 중 하나로, 텍스트가 특정 줄 수(line)를 넘어갈 경우 그 뒷부분을 자르고 말줄임표(...) 처리를 해주는 기능
  • 페이지 인디케이터 : 사용자에게 "현재 전체 중 어느 위치에 있는지"를 시각적으로 알려주는 UI 요소
  • Optimistic Update (낙관적 업데이트): 서버의 응답을 기다리지 않고 UI를 먼저 성공 상태로 변경한 뒤, 서버 요청이 실패할 경우에만 이전 상태로 되돌리는 UX 최적화 기법.
  • Inversion of Control (제어의 역전 - 폼 처리): useFormContext 등을 통해 하위 컴포넌트가 직접 폼 상태를 구독하게 하여 Props Drilling을 방지하는 구조
  • Safe Area: 모바일 기기의 노치(Notch)나 하단 바 등 화면의 기능적 요소에 콘텐츠가 가려지지 않도록 확보된 안전한 표시 영역