본문으로 건너뛰기

🍓 ReadyBerry

간편 선결제 테이크아웃 서비스
"고객이 미리 주문/결제하고 매장에서 바로 픽업 또는 테이블에 주문 전달 가능"한 서비스를 목표로 개발


  1. 프로젝트 개요
  2. 주요 기능
  3. 구성
  4. 기술 스택
  5. 담당 역할
  6. 핵심 기술 도전
  7. 주요 성과
  8. 배운 점
  9. 관련 링크

📋 프로젝트 개요

항목내용
기간2023.12 ~ 2024.05 (5개월)
팀 구성BE 2명, FE 4명, PM 1명, Designer 1명, Marketer 1명
역할Frontend Developer
기여도17% (결제 시스템 / 매장 관리 화면 담당)
배포AWS EC2 + NginX

🎯 주요 기능

고객 사이드

  • 🔐 소셜 로그인: Kakao OAuth2 연동
  • 🛒 선결제 테이크아웃: 메뉴 선택 → 결제 → 픽업
  • 🍽️ 테이블 오더: QR 코드 스캔 → 주문 → 알림
  • 💳 결제: Toss Payments SDK 연동 (카드/간편결제)
  • 🎟️ 쿠폰/포인트: 할인 및 적립 시스템

사장님 사이드

  • 📊 매장 관리: 정보 수정, 영업 시간 설정
  • 🍔 메뉴 관리: 메뉴 등록/수정/삭제, 카테고리 관리
  • 📦 주문 확인: 실시간 주문 알림, 상태 관리
  • 💰 매출 통계: 일별/월별 매출 현황

🏗️ 구성

화면 구성

figma


디렉토리 구조

// 사장님
src/
├── components/
│ ├── views/ # 공통 뷰
│ └── Sales/ # 매출 관리 차트
├── pages/
│ ├── OrderManage/
│ ├── ...
├── hooks/
├── Atom/
├── util/
└── constants/

// 고객
src/
├── components/
│ ├── Authentication/
│ └── views/
├── pages/
│ ├── HomePage/
│ ├── PaymentPage/
│ └── MyPage/
├── hooks/
├── Atom/
├── utils/
└── constants/

핵심 설계 원칙:

  • 관심사 분리: UI 컴포넌트와 비즈니스 로직(Custom Hooks) 분리
  • 재사용성 강화: Header, NavBar 등 공통 요소를 views/ 또는 components/ 하위로 분리
  • 상태 관리 최적화: 전역(Recoil) / 서버(React Query) 상태 명확히 구분하여 불필요한 리랜더링 방지

ERD

ERD_Customer - Customer ERD
ERD - Owner ERD

🔧 기술 스택

Frontend

기술선택 이유
React + Vite빠른 빌드 속도, HMR 지원으로 개발 생산성 향상
Recoil간결한 API, 비동기 처리 용이, 작은 번들 사이즈
React Query서버 상태 캐싱, 자동 재검증, 로딩/에러 상태 관리
React Hook Form성능 최적화된 폼 처리, 유효성 검사 간소화
Toss Payments SDK국내 환경 최적화 PG 연동
AxiosInterceptor로 토큰/에러 중앙 관리

Backend

  • Java 17 + Spring Boot 3.x: 안정적인 서버 개발
  • JPA + MySQL: 데이터 영속성 관리
  • Redis: 세션 관리

🎯 담당 역할

1. Toss Payments SDK 통합 및 결제 시스템 구축

문제: Toss Payments Widget 초기화 시 3~5초 소요, 쿠폰/포인트 적용 시 화면 깜빡임

해결: useEffect 최적화 + useRef 활용

// pages/PaymentPage.jsx
const PaymentPage = () => {
const paymentWidgetRef = useRef(null);
const paymentMethodsWidgetRef = useRef(null);

const paymentRequest = () => {
const paymentWidget = paymentWidgetRef.current;
const paymentMethodsWidget = paymentMethodsWidgetRef.current;

requestPayment(cartId, couponId, paymentWidget, paymentMethodsWidget, usedPoint);
};

// SDK 초기화 (마운트 시 1회만)
useEffect(() => {
(async () => {
const paymentWidget = await loadPaymentWidget(clientKey, customerKey);
paymentWidgetRef.current = paymentWidget;

const paymentMethodsWidget = paymentWidget.renderPaymentMethods(...);
paymentMethodsWidgetRef.current = paymentMethodsWidget;
})();
}, []); // 의존성 배열 비움 → 1회만 실행

return (


결제하기

);
};
// hooks/useRequestPayment
const useRequestPayment = () => {
const requestPayment = async (
cartId,
couponId,
paymentWidget,
paymentMethodsWidget,
point
) => {
try {
// 1. 서버에 결제 정보 전송
const response = await api.post("/api/payments/prepare", {
cartId,
couponId,
point,
});

// 2. 서버가 계산한 최종 금액으로 위젯 업데이트
paymentMethodsWidget.updateAmount(Math.max(response.data.amount, 0));

// 3. Toss 결제창 호출
paymentWidget?.requestPayment(response.data);
} catch (error) {
console.error("결제 실패:", error);
}
};

return requestPayment;
};

핵심 개선

  • SDK 초기화는 마운트 시 1회만 실행 (useEffect([]))
  • 결제 버튼 클릭 시 서버 금액 기반으로 updateAmount 호출
  • useRef로 SDK 인스턴스 값을 관리 → 불필요한 리렌더링 제거

상세 분석 보기 →


2. 홈/마이페이지, 매장 관리 화면 전체 개발

  • 마이페이지: 쿠폰/포인트/주문 내역 조회
  • 매장 관리: 정보 관리, 메뉴 관리, 주문 확인 UI

3. Recoil 기반 전역 상태관리 및 React Query 데이터 페칭 최적화

// Atom 정의
export const cartState = atom({
key: "cartState",
default: [],
});

export const selectedCouponState = atom({
key: "selectedCouponState",
default: null,
});

// 서버 상태 관리 (React Query)
const useMenu = (storeId) => {
return useQuery({
queryKey: ["menu", storeId],
queryFn: () => api.get(`/api/stores/${storeId}/menu`),
staleTime: 1000 * 60 * 5, // 5분간 캐시 유지
});
};

🔥 핵심 기술 도전

결제 페이지 로딩 시간 70% 단축

문제: Toss SDK 초기화에 3~5초 소요, useEffect 중복 실행

해결: useEffect 의존성 최적화 + useRef 활용

항목BeforeAfter개선율
결제 페이지 로딩3~5초1초약 70% ↓
useEffect 실행 횟수8~12회1회약 90% ↓
화면 깜빡임✅ 발생❌ 제거100% 개선

상세 분석 보기 →


📊 주요 성과

실사용 배포 및 운영

  • 교내 축제에서 테이블 오더 서비스 운영
  • 학교 인근 카페 2곳 실사용 배포 및 실제 운영
  • ✅ 사용자 피드백 기반 UI 개선 → 주문 완료 이탈율 감소

기술적 성과

  • ✅ Toss SDK 최적화로 결제 페이지 로딩 시간 70% 단축
  • ✅ useEffect 중복 실행 문제 해결 → 화면 깜빡임 제거
  • ✅ Recoil + React Query로 전역/서버 상태 명확히 분리

💡 배운 점

비즈니스적 배움

  • 기획–디자인–백엔드–마케팅과의 협업 과정을 통해, 서비스 전반을 바라보는 시야 확보
  • 실제 점주 및 사용자 피드백을 반영하며, 비즈니스 관점에서 기능을 우선순위화하는 경험

기술적 배움

  • useEffect 의존성 관리 중요성
    • 외부 라이브러리는 React의 렌더링 사이클과 분리해서 관리
  • 비동기 작업과 Race Condition 처리 경험
    • 두 개의 useEffect가 동시에 실행되면 경합 조건 발생 가능
    • 단일 진입점(결제 버튼 클릭)에서 모든 작업을 순차적으로 처리
  • 백엔드 개발 전환 계기
    • API 설계 과정에서 데이터 정합성, 트랜잭션 처리의 중요성 인식
    • 프론트엔드는 "표현 계층"이지만, 실제 비즈니스 로직은 백엔드에서 처리됨을 체감
    • 결제/주문 도메인의 복잡성을 이해하며 백엔드 개발에 대한 관심 증대

🔗 관련 링크