Conversation
Walkthrough인증, 장바구니, 메뉴 모듈을 클라이언트/서버 변형( Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Browser (Client)
participant CartService as cartService.server
participant API as /api/cart (apiServer)
Client->>CartService: 호출 (getCartInfo)
CartService->>API: GET /api/cart
API-->>CartService: CartApiResponse
CartService-->>Client: CartInfo (toCartInfo 변환)
Client->>Client: CartView 렌더링 (EmptyCartView 또는 CartMenuList + PaymentBar)
Client->>Router: PaymentBar 클릭 → /order 네비게이션
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧬 Code graph analysis (1)src/features/menu/types/hakgwanType.ts (2)
🔇 Additional comments (1)
✏️ Tip: You can disable this entire section by setting Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/features/menu/components/MenuPageContainer.tsx`:
- Line 3: The client component MenuPageContainer.tsx is importing
HakgwanMenuData from hakgwanMenuService.server which causes a "Server-only
module imported in Client Component" build error; extract the HakgwanMenuData
interface into a shared types module (e.g., menuType.ts), remove the type
definition from hakgwanMenuService.server, and update MenuPageContainer.tsx to
use an import type { HakgwanMenuData } from the new types module so the client
only imports a type.
🧹 Nitpick comments (7)
src/features/cart/services/cartService.server.ts (1)
1-9: LGTM!서버 서비스 모듈이 올바르게 구현되었습니다.
"server-only"import로 서버 전용 코드임을 명확히 하고,cartApi.server에서 데이터를 가져와toCartInfo로 변환하는 흐름이 적절합니다.선택적 개선: 함수 시그니처에서 콜론 뒤에 공백을 추가하면 일관성이 향상됩니다.
♻️ 스타일 개선 제안
-export async function getCartInfo():Promise<CartInfo> { +export async function getCartInfo(): Promise<CartInfo> {src/features/cart/components/bar/PaymentBar.tsx (1)
18-23: 버튼에type="button"속성 추가를 권장합니다.버튼의 기본 type은
"submit"이므로, 폼 내부에서 사용될 경우 의도치 않은 폼 제출이 발생할 수 있습니다. 명시적으로type="button"을 지정하면 이런 문제를 방지할 수 있습니다.♻️ 제안된 수정
<button + type="button" onClick={onClick} className={styles.button} >src/features/cart/hooks/useCart.ts (1)
65-68:getMenuQuantity에서sortedCartInfo사용은 불필요합니다.
getMenuQuantity는menuId로 아이템을 찾는 함수이므로 정렬 여부와 무관합니다.cartInfo를 직접 사용해도 동일한 결과를 얻을 수 있으며, 이 경우sortedCartInfo가 변경될 때마다 불필요한getMenuQuantity재생성을 피할 수 있습니다.다만, 현재 구현도 기능적으로 문제는 없으므로 선택적으로 고려해 주세요.
src/app/(header-only)/order/page.tsx (1)
5-22: 임시 페이지 - 스타일 일관성 및 접근성 개선을 권장합니다.임시 주문 페이지로 보이지만, 몇 가지 개선점이 있습니다:
- 스타일 일관성: 프로젝트 내 다른 컴포넌트는 CSS 모듈을 사용하는데, 이 페이지는 인라인 Tailwind 클래스(
bg-blue-400)를 사용하고 있습니다.- 접근성: 버튼에
type="button"속성 추가를 권장합니다.- 불필요한 세미콜론: Line 22의 함수 끝 세미콜론은 불필요합니다.
♻️ 제안된 수정
-export default function OrderPage() { +export default function OrderPage() { const router = useRouter(); const onClick = () => { router.replace("/"); - } + }; + return ( <div> 주문 페이지 입니다 <button + type="button" className="bg-blue-400" onClick={onClick} > 홈 화면 이동 </button> </div> ); -}; +}src/features/cart/components/view/CartView.module.css (1)
1-20: 모바일 안전 영역 고려를 추가하면 더 견고합니다.홈 인디케이터 영역이 있는 기기(iOS 등)에서 바가 겹치지 않도록 safe-area inset을 반영하는 편이 좋습니다.
♻️ 제안 diff
.container { display: flex; flex-direction: column; justify-content: center; width: 100%; padding-inline: 1.25rem; padding-top: 0.75rem; - padding-bottom: var(--payment-bar-height); + padding-bottom: calc(var(--payment-bar-height) + env(safe-area-inset-bottom)); } .paymentBarWrapper { position: fixed; - bottom: 0; + bottom: env(safe-area-inset-bottom); left: 50%; transform: translateX(-50%); width: 100%; max-width: var(--container-3xl); }src/app/(header-only)/cart/page.tsx (1)
7-10: CartView에 초기 cartInfo 전달 검토서버에서
cartInfo를 가져오지만CartView에는 전달하지 않아 클라이언트에서 다시 요청할 가능성이 있습니다. 중복 호출/일시적 불일치(서버 판정과 클라이언트 상태 차이)를 피하려면 초기 데이터를CartView에 넘기거나 공용 상태로 재사용하는 방식을 고려해주세요.src/features/cart/components/list/CartMenuList.tsx (1)
16-33: 리스트 key로 cartItemId 사용 권장동일 메뉴가 옵션 등으로 여러 개 담길 가능성이 있으면
menuId는 중복될 수 있어 리렌더/상태 꼬임 위험이 있습니다.cartItemId가 제공되므로 더 안정적인 key로 사용하는 편이 안전합니다.가능한 수정 예시
- <React.Fragment key={item.menuId}> + <React.Fragment key={item.cartItemId}>
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
src/app/(header-nav)/hakgwan/page.tsxsrc/app/(header-nav)/my/page.tsxsrc/app/(header-only)/cart/page.tsxsrc/app/(header-only)/order/page.tsxsrc/features/auth/api/loginApi.client.tssrc/features/auth/api/logoutApi.client.tssrc/features/auth/components/form/LoginForm.tsxsrc/features/cart/api/cartApi.client.tssrc/features/cart/api/cartApi.server.tssrc/features/cart/components/bar/PaymentBar.module.csssrc/features/cart/components/bar/PaymentBar.tsxsrc/features/cart/components/card/CartMenuCard.module.csssrc/features/cart/components/list/CartMenuList.module.csssrc/features/cart/components/list/CartMenuList.tsxsrc/features/cart/components/view/CartView.module.csssrc/features/cart/components/view/CartView.tsxsrc/features/cart/hooks/useCart.tssrc/features/cart/services/cartService.client.tssrc/features/cart/services/cartService.server.tssrc/features/menu/api/cafeteriaApi.server.tssrc/features/menu/api/categoryApi.server.tssrc/features/menu/api/menuApi.server.tssrc/features/menu/components/MenuPageContainer.tsxsrc/features/menu/components/bar/OrderSummaryBar.tsxsrc/features/menu/services/hakgwanMenuService.server.ts
💤 Files with no reviewable changes (1)
- src/features/cart/components/card/CartMenuCard.module.css
🧰 Additional context used
🧬 Code graph analysis (20)
src/features/cart/components/list/CartMenuList.module.css (2)
src/features/menu/components/list/MenuList.tsx (3)
MenuList(12-40)item(18-37)MenuListProps(7-10)src/features/cart/components/card/CartMenuCard.tsx (2)
CartMenuCard(21-61)CartMenuCardProps(11-19)
src/features/cart/api/cartApi.client.ts (3)
src/features/cart/api/cartApi.ts (2)
upsertCart(16-18)getCart(8-10)src/features/cart/types/cartType.ts (4)
CartInfo(31-36)CartApiResponse(15-20)CartItemInfo(22-29)CartItemApiResponse(6-13)src/shared/lib/api/apiClient.ts (1)
ApiClient(8-75)
src/features/menu/components/MenuPageContainer.tsx (1)
src/features/menu/services/hakgwanMenuService.ts (1)
HakgwanMenuData(10-13)
src/features/menu/services/hakgwanMenuService.server.ts (2)
src/features/menu/services/hakgwanMenuService.ts (2)
getHakgwanMenus(15-32)HakgwanMenuData(10-13)src/features/menu/api/menuApi.ts (1)
getAllMenuByCafeteriaId(4-8)
src/features/auth/components/form/LoginForm.tsx (2)
src/features/auth/api/loginApi.ts (1)
login(4-6)src/features/auth/types/loginTypes.ts (2)
LoginApiResponse(6-13)LoginRequest(1-4)
src/app/(header-only)/cart/page.tsx (2)
src/features/cart/components/view/EmptyCartView.tsx (1)
EmptyCartView(5-38)src/features/cart/services/cartService.ts (1)
getCartInfo(5-8)
src/features/auth/api/loginApi.client.ts (6)
src/features/auth/api/loginApi.ts (1)
login(4-6)src/shared/lib/api/apiClient.ts (1)
ApiClient(8-75)src/features/auth/types/loginTypes.ts (3)
LoginApiResponse(6-13)LoginRequest(1-4)LoginResponse(15-19)src/features/auth/api/logoutApi.ts (1)
logout(3-5)src/app/(plain)/login/page.tsx (1)
LoginPage(4-13)src/app/api/auth/login/route.ts (1)
POST(9-52)
src/features/cart/api/cartApi.server.ts (5)
src/features/cart/api/cartApi.client.ts (1)
getCart(9-11)src/features/cart/types/cartType.ts (3)
CartApiResponse(15-20)CartInfo(31-36)CartItemApiResponse(6-13)src/shared/lib/api/apiServer.ts (1)
apiServer(160-160)src/features/cart/api/cartApi.ts (2)
getCart(8-10)upsertCart(16-18)src/features/cart/services/cartService.ts (1)
upsertCartInfo(10-13)
src/features/cart/components/view/CartView.tsx (5)
src/features/cart/hooks/useCart.ts (1)
useCart(24-174)src/features/cart/components/view/EmptyCartView.tsx (1)
EmptyCartView(5-38)src/features/cart/components/list/CartMenuList.tsx (1)
CartMenuList(11-36)src/features/cart/components/bar/PaymentBar.tsx (1)
PaymentBar(9-26)src/features/cart/components/card/CartMenuCard.tsx (2)
CartMenuCard(21-61)CartMenuCardProps(11-19)
src/features/cart/services/cartService.server.ts (3)
src/features/cart/types/cartType.ts (2)
CartInfo(31-36)CartApiResponse(15-20)src/features/cart/api/cartApi.server.ts (1)
getCart(9-11)src/features/cart/utils/cartMapper.ts (2)
toCartInfo(3-12)toCartItemInfo(14-23)
src/features/menu/api/menuApi.server.ts (3)
src/features/menu/api/menuApi.ts (1)
getAllMenuByCafeteriaId(4-8)src/features/menu/types/menuType.ts (1)
MenuApiResponse(3-10)src/shared/lib/api/apiServer.ts (1)
ApiServer(21-157)
src/features/auth/api/logoutApi.client.ts (3)
src/features/auth/api/logoutApi.ts (1)
logout(3-5)src/app/api/auth/logout/route.ts (1)
POST(7-29)src/shared/lib/api/apiClient.ts (1)
ApiClient(8-75)
src/features/cart/components/bar/PaymentBar.tsx (1)
src/shared/utils/number/utils.ts (1)
formatNumberWithComma(4-8)
src/features/cart/hooks/useCart.ts (1)
src/features/cart/types/cartType.ts (2)
CartItemInfo(22-29)CartInfo(31-36)
src/features/cart/services/cartService.client.ts (3)
src/features/cart/api/cartApi.ts (2)
upsertCart(16-18)getCart(8-10)src/features/cart/services/cartService.ts (2)
upsertCartInfo(10-13)getCartInfo(5-8)src/features/cart/types/cartType.ts (1)
CartInfo(31-36)
src/features/menu/api/cafeteriaApi.server.ts (7)
src/features/menu/types/cafeteriaType.ts (1)
CafeteriaApiResponse(1-6)src/features/menu/api/menuApi.ts (1)
getAllMenuByCafeteriaId(4-8)src/features/menu/api/cafeteriaApi.ts (1)
getAllCafeteria(4-6)src/features/menu/types/categoryType.ts (1)
CategoryApiResponse(1-5)src/features/menu/services/hakgwanMenuService.ts (2)
cafeteria(18-18)getHakgwanMenus(15-32)src/features/menu/types/menuType.ts (1)
MenuApiResponse(3-10)src/shared/lib/api/apiServer.ts (1)
ApiServer(21-157)
src/features/menu/components/bar/OrderSummaryBar.tsx (4)
src/features/cart/components/view/EmptyCartView.tsx (1)
EmptyCartView(5-38)src/shared/components/header/Header.tsx (1)
Header(5-18)src/features/cart/components/card/CartMenuCard.tsx (1)
CartMenuCard(21-61)src/features/menu/components/button/CartButton.tsx (1)
CartButton(10-32)
src/app/(header-nav)/my/page.tsx (2)
src/features/auth/api/logoutApi.ts (1)
logout(3-5)src/app/api/auth/logout/route.ts (1)
POST(7-29)
src/features/cart/components/list/CartMenuList.tsx (4)
src/features/cart/types/cartType.ts (2)
CartInfo(31-36)CartItemInfo(22-29)src/features/cart/components/card/CartMenuCard.tsx (2)
CartMenuCard(21-61)CartMenuCardProps(11-19)src/shared/components/bar/SeparationBar.tsx (1)
SeparationBar(3-7)src/features/menu/components/list/MenuList.tsx (3)
MenuList(12-40)item(18-37)MenuListProps(7-10)
src/features/menu/api/categoryApi.server.ts (4)
src/features/menu/types/categoryType.ts (2)
CategoryApiResponse(1-5)CategoryItem(7-10)src/features/menu/types/menuType.ts (1)
MenuApiResponse(3-10)src/features/menu/services/hakgwanMenuService.ts (1)
HakgwanMenuData(10-13)src/shared/lib/api/apiServer.ts (1)
ApiServer(21-157)
🔇 Additional comments (18)
src/features/cart/api/cartApi.client.ts (1)
1-19: LGTM!클라이언트 API 모듈로 올바르게 분리되었습니다.
"use client"지시문이 파일 상단에 적절히 배치되었고,apiClient를 사용하여 장바구니 API 호출을 수행합니다.src/features/cart/services/cartService.client.ts (1)
1-14: LGTM!클라이언트 서비스 모듈이 올바르게 구성되었습니다.
"use client"지시문과 함께cartApi.client에서 API 함수를 import하여 클라이언트/서버 경계가 일관되게 유지됩니다.src/features/menu/components/bar/OrderSummaryBar.tsx (1)
21-23: LGTM!
router.replace에서router.push로의 변경이 적절합니다. 사용자가 장바구니 페이지에서 브라우저 뒤로 가기 버튼을 통해 메뉴 페이지로 돌아갈 수 있어 UX가 개선됩니다.src/features/cart/api/cartApi.server.ts (1)
1-11: LGTM!서버 전용 API 모듈이 올바르게 구성되었습니다.
"server-only"import와apiServer사용이 적절합니다.참고: 클라이언트 API(
cartApi.client.ts)에는upsertCart함수가 포함되어 있지만 서버 API에는getCart만 있습니다. 장바구니 수정이 클라이언트에서만 발생한다면 현재 구조가 적절하지만, 향후 서버 사이드에서 장바구니 수정이 필요할 경우 추가를 고려해 주세요.src/features/cart/components/bar/PaymentBar.tsx (1)
9-25: LGTM!컴포넌트 구조가 깔끔하고, props 인터페이스가 잘 정의되어 있습니다.
formatNumberWithComma를 활용한 가격 포맷팅도 적절합니다.src/features/cart/components/bar/PaymentBar.module.css (2)
1-39: LGTM!스타일 정의가 깔끔하고, 디자인 시스템 변수를 일관성 있게 활용하고 있습니다.
41-43:@theme블록 사용은 유효합니다.Tailwind CSS v4에서 도입된 공식 문법이며, 프로젝트 전체에서 일관되게 사용되고 있습니다 (colors.css, motion.css, layouts.css 등). 코드에 문제가 없습니다.
src/features/cart/hooks/useCart.ts (1)
40-50: LGTM!
useMemo를 활용한 정렬 로직이 적절합니다. 원본 배열을 변경하지 않고 spread operator로 새 배열을 생성한 후 정렬하는 방식이 올바릅니다.src/features/auth/api/logoutApi.client.ts (1)
1-6: LGTM!클라이언트/서버 API 분리 패턴에 맞게
"use client"지시자가 올바르게 추가되었습니다. 다른 클라이언트 API 파일들(loginApi.client.ts,cartApi.client.ts)과 일관된 구조입니다.src/features/auth/api/loginApi.client.ts (1)
1-7: 클라이언트 모듈 분리 방향과 잘 맞습니다.
"use client"추가로 실행 환경이 명확해져 좋아요.src/app/(header-nav)/my/page.tsx (1)
3-5: 임포트 경로 변경이 의도와 일치합니다.클라이언트 전용 API 모듈로의 전환이 자연스럽습니다.
src/features/auth/components/form/LoginForm.tsx (1)
10-15: 클라이언트 API 분리와 일관됩니다.로직 변경 없이 경로만 정리되어 안정적입니다.
src/features/cart/components/view/CartView.tsx (1)
1-39: 장바구니 비어있음/채워짐 분기 처리가 명확합니다.뷰 구성과 결제 바 노출 흐름이 이해하기 좋습니다.
src/features/menu/api/categoryApi.server.ts (1)
1-6: 서버 전용 경계 명확화 👍
server-only추가로 서버 실행 맥락이 명확해졌고, 기존 API 호출 로직도 그대로라 문제 없어 보입니다.src/features/menu/api/menuApi.server.ts (1)
1-8: 서버 전용 모듈 정렬 OK
server-only추가로 서버 경계가 명확해졌고,requireAuth설정도 그대로 유지되어 안정적입니다.src/features/menu/api/cafeteriaApi.server.ts (1)
1-6: 서버 전용화 반영 확인 완료서버 전용 지시자 추가 외에 동작 변화가 없어 안전한 변경입니다.
src/app/(header-nav)/hakgwan/page.tsx (1)
3-3: 서버 전용 서비스로의 import 전환 OK페이지가 서버 전용 서비스에 직접 의존하도록 정리되어 일관성이 좋아졌습니다.
src/features/menu/services/hakgwanMenuService.server.ts (1)
1-3: 서버 API 모듈 통일 처리 완료server 전용 API로의 import 정리가 잘 되어 있습니다. 로직 영향 없이 경계만 명확화되었습니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| "use client"; | ||
|
|
||
| import { HakgwanMenuData } from "@/features/menu/services/hakgwanMenuService"; | ||
| import { HakgwanMenuData } from "@/features/menu/services/hakgwanMenuService.server"; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Next.js Client Component importing .server module behavior error
💡 Result:
Short answer: Next.js prevents importing a .server (server-only) module into a Client Component — that pattern is unsupported and triggers a build/runtime error because client code cannot include server-only logic or secrets. See Next.js docs and guidance. [1][2]
Why this happens (brief):
- Server components can rely on Node/server-only APIs and secrets; bundling them into client code would leak or fail. Next enforces a one-way dependency: Server → Client, not Client → Server. [1][2]
- Next.js also provides the server-only package to "poison" modules and produce a clear build-time error if a server-only module is imported into client code. [1][2]
How to fix / recommended patterns (pick one):
- Move the import up into a Server Component (wrap the Client Component with a Server parent) and pass needed data/props down to the Client Component. [2]
- Keep the module server-only (import 'server-only') so accidental client imports fail fast. [1][2]
- If you must run client-side logic that depends on server data, fetch the data via an API route or fetch from the client (or use a server Action) and pass results into the client component. [2]
- Don’t mark server-only modules with 'use client'; instead refactor to separate client and server responsibilities. [2][3]
References:
- Next.js docs — Server and Client Components (preventing environment poisoning / server-only). [1]
- Next.js docs — Composition patterns / unsupported pattern: importing Server into Client. [2]
- Community discussion showing examples and rationale. [3]
If you want, share the exact error message and a short code snippet and I’ll point to the precise change to fix it.
🏁 Script executed:
# Check the actual file being reviewed
cat -n src/features/menu/components/MenuPageContainer.tsxRepository: CampusTable/campus-table-fe
Length of output: 3495
🏁 Script executed:
# Look for the hakgwanMenuService.server module
find . -name "*hakgwanMenuService*" -type fRepository: CampusTable/campus-table-fe
Length of output: 128
🏁 Script executed:
# Search for how HakgwanMenuData is used in the file
rg "HakgwanMenuData" src/features/menu/components/MenuPageContainer.tsx -A 3 -B 1Repository: CampusTable/campus-table-fe
Length of output: 461
🏁 Script executed:
# Check what's exported from the server module
head -50 src/features/menu/services/hakgwanMenuService.server.tsRepository: CampusTable/campus-table-fe
Length of output: 1648
🏁 Script executed:
# Check the existing types module
cat -n src/features/menu/types/menuType.tsRepository: CampusTable/campus-table-fe
Length of output: 549
클라이언트 컴포넌트에서 .server 모듈 import는 빌드 에러 발생
"use client" 파일에서 .server 모듈을 가져오면 Next.js에서 빌드 시점에 "Server-only module imported in Client Component" 오류가 발생합니다. HakgwanMenuData는 타입으로만 사용되므로, 공유 타입 모듈로 옮기고 import type으로 분리해야 합니다.
🛠️ 수정 방법
-import { HakgwanMenuData } from "@/features/menu/services/hakgwanMenuService.server";
+import type { HakgwanMenuData } from "@/features/menu/types/menuType";HakgwanMenuData 인터페이스를 src/features/menu/types/menuType.ts에 추가하고, 기존 hakgwanMenuService.server.ts에서는 타입 정의를 제거하세요.
🤖 Prompt for AI Agents
In `@src/features/menu/components/MenuPageContainer.tsx` at line 3, The client
component MenuPageContainer.tsx is importing HakgwanMenuData from
hakgwanMenuService.server which causes a "Server-only module imported in Client
Component" build error; extract the HakgwanMenuData interface into a shared
types module (e.g., menuType.ts), remove the type definition from
hakgwanMenuService.server, and update MenuPageContainer.tsx to use an import
type { HakgwanMenuData } from the new types module so the client only imports a
type.
✨ 변경 사항
✅ 테스트
Summary by CodeRabbit
새로운 기능
UI/스타일 개선
동작 개선
✏️ Tip: You can customize this high-level summary in your review settings.