Skip to content

Conversation

@leeleeleeleejun
Copy link
Member

@leeleeleeleejun leeleeleeleejun commented Nov 2, 2025

#️⃣연관된 이슈

#51

📝작업 내용

1. HydrationBoundary를 사용하는 페이지 force-dynamic 추가 e77bb60

Next.js는 기본적으로 페이지를 **SSG(정적 렌더링)**로 빌드하며, 이 시점에 prefetch로 설정한 API 데이터를 미리 요청합니다.
하지만 현재 API는 MSW로 구현되어 있어, next build가 실행될 때는 모킹 API 서버가 동작하지 않습니다.
이로 인해 빌드 과정에서 API 요청이 500 에러 등을 반환하여 빌드가 실패했습니다.

현재 SSG 빌드 실패를 우회하기 위해, 데이터 페칭이 필요한 페이지에 export const dynamic = 'force-dynamic' 플래그를 추가했습니다.
렌더링 전략을 SSR로 변경하여, 빌드 시점의 API 호출을 건너뛰고 배포가 가능하도록 합니다.

2. 동적 지도 렌더링을 위한 MapComponent 및 MapContainer 추가 c66d438

지도를 사용하는 페이지의 경우, window 객체가 없는 서버 환경에서 렌더링을 시도하다 window is not defined 에러가 발생했습니다.
이는 next/dynamic과 ssr: false 옵션을 사용해, 해당 컴포넌트를 클라이언트 사이드에서만 로드하도록 하여 해결했습니다.

3. 페이지 metaData 추가

스크린샷 (선택)

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • SEO 최적화를 위한 메타데이터 추가 (페이지 제목, 설명, Open Graph 정보)
    • 카테고리 상세 페이지에 동적 문서 제목 업데이트 기능 추가
  • 버그 수정

    • URL 기반 카테고리 선택으로 페이지 상태 관리 개선
    • 페이지 네비게이션 안정성 향상
  • 성능 개선

    • 여러 페이지에 동적 렌더링 활성화
    • 지도 구성요소 지연 로딩 최적화

@leeleeleeleejun leeleeleeleejun linked an issue Nov 2, 2025 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Nov 2, 2025

🚀 코드 리뷰 분석

Walkthrough

다양한 페이지 컴포넌트에 Next.js 메타데이터 및 동적 렌더링 설정을 추가하고, 카테고리 상세 페이지를 경로 기반 상태 해결로 리팩토링하며, 지도 컴포넌트를 SSR 비활성화로 재구성합니다.

Changes

Cohort / File(s) Summary
메타데이터 및 동적 렌더링 설정
apps/web/app/page.tsx, apps/web/app/events/food-slot/page.tsx, apps/web/app/events/lucky-draw/page.tsx, apps/web/app/likes/page.tsx, apps/web/app/places/new/page.tsx, apps/web/app/requests/[id]/page.tsx, apps/web/app/requests/page.tsx
각 파일에 export const dynamic = 'force-dynamic' 추가하여 정적 최적화 비활성화. 일부 파일에는 메타데이터 객체 추가 (제목, 설명, OpenGraph 필드 포함).
레이아웃 메타데이터 강화
apps/web/app/layout.tsx
SITE_URL 상수 추가 및 메타데이터 객체를 metadataBase, 구조화된 title 객체, openGraph 필드가 있는 형태로 재구성하여 SEO 최적화.
카테고리 상세 페이지 리팩토링
apps/web/app/categories/[id]/CategoryDetailPage.tsx, apps/web/app/categories/[id]/page.tsx
CategoryDetailPage를 props 없는 컴포넌트로 변환, URL 기반 카테고리 해결 구현. page.tsx에 generateMetadata 함수 추가 및 Page 컴포넌트 서명 수정.
지도 컴포넌트 SSR 비활성화
apps/web/app/map/MapComponent.tsx, apps/web/app/map/MapContainer.tsx, apps/web/app/map/page.tsx
MapComponent로 컴포넌트 이름 변경 및 기본 내보내기로 변환. 새로운 MapContainer 파일 생성하여 동적 import로 SSR 비활성화. page.tsx에 메타데이터 및 MapContainer 사용 추가.
장소 메타데이터 생성
apps/web/app/places/[id]/page.tsx
getPlaceDetail을 이용한 generateMetadata 함수 추가하여 동적 메타데이터 생성 구현.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 주의 필요 영역:
    • CategoryDetailPage의 경로 기반 상태 해결 로직 및 useEffect 구현 검증 필요
    • 모든 동적 렌더링 설정이 의도된 페이지에만 적용되었는지 확인
    • 메타데이터 생성 함수들의 데이터 페칭 에러 처리 검증
    • MapContainer 동적 import 및 Suspense 경계 구현의 올바름 확인

Possibly related PRs

Suggested labels

✨ Feature, 🎯 SEO, ♻️ Refactor

Poem

🐰 경로를 따라 카테고리가 춤을 추고,
메타데이터는 SEO의 향연을 펼치네.
동적 렌더링으로 페이지는 생생하게,
지도는 SSR 없이 가볍게 날아다니고,
토끼는 모든 변화를 축하하며 깡충! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 "Feat/#51 Vercel 배포 관련 작업"은 PR의 실제 변경 사항과 관련이 있습니다. PR 목표에 명시된 대로, 이 변경 사항들은 Vercel 배포 시 발생하는 문제들을 해결하기 위한 것입니다: SSG 빌드 타임에 API 요청이 발생하는 문제를 해결하기 위해 force-dynamic 추가, SSR 중 "window is not defined" 오류를 해결하기 위한 MapComponent/MapContainer 리팩토링, 그리고 메타데이터 추가가 포함되어 있습니다. 다만 제목이 전체 변경 사항을 구체적으로 설명하기보다는 범위가 넓은 편이며, 구체적인 기술적 개선 사항들(동적 렌더링, 컴포넌트 구조 변경 등)을 명시하지 않습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#51

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@leeleeleeleejun leeleeleeleejun marked this pull request as ready for review November 2, 2025 06:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
apps/web/app/requests/page.tsx (1)

13-13: 불필요한 async 선언을 제거하는 것을 고려하세요.

Page 컴포넌트가 async로 선언되었으나, 내부에서 await를 사용하지 않습니다. HydrationBoundaryPageprefetch prop이 이미 비동기 함수를 처리하므로, Page 컴포넌트 자체를 async로 만들 필요는 없습니다.

다음 diff를 적용하여 불필요한 async를 제거할 수 있습니다:

-const Page = async () => {
+const Page = () => {
apps/web/app/events/food-slot/page.tsx (1)

14-23: Open Graph 타이틀 형식의 불일치가 확인되었습니다.

코드베이스 전체에서 두 가지 형식이 혼용되고 있습니다:

  • 형식 A (공주대 맛집 | ${pageTitle}): apps/web/app/events/food-slot/page.tsx:18, apps/web/app/places/[id]/page.tsx:22, apps/web/app/categories/[id]/page.tsx:21
  • 형식 B (${pageTitle} | 공주대 맛집): apps/web/app/likes/page.tsx:18, apps/web/app/map/page.tsx:13

전체 앱에서 하나의 형식으로 통일하는 것을 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f120ee8 and bced125.

⛔ Files ignored due to path filters (1)
  • apps/web/app/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (14)
  • apps/web/app/categories/[id]/CategoryDetailPage.tsx (3 hunks)
  • apps/web/app/categories/[id]/page.tsx (1 hunks)
  • apps/web/app/events/food-slot/page.tsx (1 hunks)
  • apps/web/app/events/lucky-draw/page.tsx (1 hunks)
  • apps/web/app/layout.tsx (1 hunks)
  • apps/web/app/likes/page.tsx (1 hunks)
  • apps/web/app/map/MapComponent.tsx (2 hunks)
  • apps/web/app/map/MapContainer.tsx (1 hunks)
  • apps/web/app/map/page.tsx (1 hunks)
  • apps/web/app/page.tsx (1 hunks)
  • apps/web/app/places/[id]/page.tsx (1 hunks)
  • apps/web/app/places/new/page.tsx (1 hunks)
  • apps/web/app/requests/[id]/page.tsx (1 hunks)
  • apps/web/app/requests/page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
apps/web/app/map/MapContainer.tsx (2)
apps/web/app/map/MapPage.tsx (1)
  • map (21-104)
apps/web/app/NaverMapProvider.tsx (1)
  • children (6-10)
apps/web/app/requests/[id]/page.tsx (6)
apps/web/app/likes/page.tsx (1)
  • dynamic (9-9)
apps/web/app/events/food-slot/page.tsx (1)
  • dynamic (8-8)
apps/web/app/events/lucky-draw/page.tsx (1)
  • dynamic (11-11)
apps/web/app/page.tsx (1)
  • dynamic (23-23)
apps/web/app/places/new/page.tsx (1)
  • dynamic (53-53)
apps/web/app/requests/page.tsx (1)
  • dynamic (11-11)
apps/web/app/places/new/page.tsx (6)
apps/web/app/likes/page.tsx (1)
  • dynamic (9-9)
apps/web/app/events/food-slot/page.tsx (1)
  • dynamic (8-8)
apps/web/app/events/lucky-draw/page.tsx (1)
  • dynamic (11-11)
apps/web/app/page.tsx (1)
  • dynamic (23-23)
apps/web/app/requests/[id]/page.tsx (1)
  • dynamic (5-5)
apps/web/app/requests/page.tsx (1)
  • dynamic (11-11)
apps/web/app/likes/page.tsx (6)
apps/web/app/events/food-slot/page.tsx (2)
  • dynamic (8-8)
  • metadata (14-23)
apps/web/app/events/lucky-draw/page.tsx (1)
  • dynamic (11-11)
apps/web/app/page.tsx (1)
  • dynamic (23-23)
apps/web/app/places/new/page.tsx (1)
  • dynamic (53-53)
apps/web/app/requests/[id]/page.tsx (1)
  • dynamic (5-5)
apps/web/app/requests/page.tsx (1)
  • dynamic (11-11)
apps/web/app/page.tsx (6)
apps/web/app/likes/page.tsx (1)
  • dynamic (9-9)
apps/web/app/events/food-slot/page.tsx (1)
  • dynamic (8-8)
apps/web/app/events/lucky-draw/page.tsx (1)
  • dynamic (11-11)
apps/web/app/places/new/page.tsx (1)
  • dynamic (53-53)
apps/web/app/requests/[id]/page.tsx (1)
  • dynamic (5-5)
apps/web/app/requests/page.tsx (1)
  • dynamic (11-11)
apps/web/app/categories/[id]/CategoryDetailPage.tsx (3)
apps/web/app/_apis/queries/category.ts (1)
  • useCategoryQueries (11-17)
apps/web/app/categories/[id]/_components/RowCategories/RowCategories.tsx (1)
  • RowCategories (12-30)
apps/web/app/categories/[id]/_components/Places/Places.tsx (1)
  • Places (8-30)
apps/web/app/events/food-slot/page.tsx (6)
apps/web/app/likes/page.tsx (2)
  • dynamic (9-9)
  • metadata (14-23)
apps/web/app/events/lucky-draw/page.tsx (1)
  • dynamic (11-11)
apps/web/app/page.tsx (1)
  • dynamic (23-23)
apps/web/app/places/new/page.tsx (1)
  • dynamic (53-53)
apps/web/app/requests/[id]/page.tsx (1)
  • dynamic (5-5)
apps/web/app/requests/page.tsx (1)
  • dynamic (11-11)
apps/web/app/categories/[id]/page.tsx (4)
apps/web/app/_apis/services/category.ts (1)
  • getCategories (5-8)
apps/web/app/HydrationBoundaryPage.tsx (1)
  • HydrationBoundaryPage (29-44)
apps/web/app/_apis/queries/category.ts (1)
  • useCategoryQueries (11-17)
apps/web/app/categories/[id]/CategoryDetailPage.tsx (1)
  • CategoryDetailPage (15-55)
apps/web/app/requests/page.tsx (3)
apps/web/app/likes/page.tsx (1)
  • dynamic (9-9)
apps/web/app/page.tsx (2)
  • dynamic (23-23)
  • Page (25-59)
apps/web/app/requests/[id]/page.tsx (1)
  • dynamic (5-5)
apps/web/app/events/lucky-draw/page.tsx (6)
apps/web/app/likes/page.tsx (1)
  • dynamic (9-9)
apps/web/app/events/food-slot/page.tsx (1)
  • dynamic (8-8)
apps/web/app/page.tsx (1)
  • dynamic (23-23)
apps/web/app/places/new/page.tsx (1)
  • dynamic (53-53)
apps/web/app/requests/[id]/page.tsx (1)
  • dynamic (5-5)
apps/web/app/requests/page.tsx (1)
  • dynamic (11-11)
apps/web/app/places/[id]/page.tsx (1)
apps/web/app/_apis/services/place.ts (1)
  • getPlaceDetail (60-63)
apps/web/app/map/page.tsx (1)
apps/web/app/map/MapContainer.tsx (1)
  • MapContainer (10-10)
🔇 Additional comments (13)
apps/web/app/page.tsx (1)

23-23: 동적 렌더링 설정이 올바르게 적용되었습니다.

force-dynamic 설정으로 빌드 시 API 호출을 방지하여 MSW 미실행 문제를 해결했습니다.

apps/web/app/requests/[id]/page.tsx (1)

5-5: LGTM!

동적 렌더링 설정이 올바르게 적용되었습니다.

apps/web/app/events/lucky-draw/page.tsx (1)

11-11: LGTM!

동적 렌더링 설정이 올바르게 적용되었습니다.

apps/web/app/events/food-slot/page.tsx (1)

8-8: LGTM!

동적 렌더링 설정이 올바르게 적용되었습니다.

apps/web/app/likes/page.tsx (2)

9-9: LGTM!

동적 렌더링 설정이 올바르게 적용되었습니다.


14-23: 메타데이터가 올바르게 구현되었습니다.

페이지 제목, 설명, Open Graph 데이터가 적절하게 설정되었습니다.

apps/web/app/requests/page.tsx (1)

11-11: LGTM!

동적 렌더링 설정이 올바르게 적용되었습니다.

apps/web/app/layout.tsx (1)

12-32: 메타데이터 구성이 잘 되어 있습니다.

Next.js 메타데이터 API를 활용하여 metadataBase, 타이틀 템플릿, 설명, Open Graph 설정이 올바르게 구성되었습니다. 타이틀 템플릿을 통해 하위 페이지에서 일관된 제목 형식을 유지할 수 있습니다.

apps/web/app/map/MapContainer.tsx (1)

1-10: SSR 비활성화 처리가 적절합니다.

Naver Maps가 window 객체를 필요로 하기 때문에 dynamic import에 ssr: false 설정을 통해 클라이언트 사이드에서만 렌더링하도록 한 것은 올바른 접근입니다. 로딩 폴백도 제공하여 사용자 경험을 개선했습니다.

apps/web/app/map/page.tsx (2)

6-18: 메타데이터가 올바르게 구성되었습니다.

페이지 타이틀, 설명, Open Graph 정보가 적절하게 설정되었습니다. 지도 페이지의 SEO 최적화에 도움이 됩니다.


20-28: MapContainer 사용이 적절합니다.

이전의 MapPage를 MapContainer로 교체하여 클라이언트 사이드 렌더링을 보장합니다. 이 페이지는 HydrationBoundary를 사용하지 않으므로 dynamic = 'force-dynamic' 설정이 필요하지 않습니다.

apps/web/app/map/MapComponent.tsx (1)

21-21: 컴포넌트 리네이밍이 적절합니다.

MapPage에서 MapComponent로 이름을 변경하고 default export로 전환하여 MapContainer의 dynamic import와 잘 연동됩니다.

apps/web/app/categories/[id]/CategoryDetailPage.tsx (1)

50-52: Suspense 래퍼 추가가 적절합니다.

Places 컴포넌트가 useSuspenseQuery를 사용하므로 Suspense 경계로 감싸는 것이 올바릅니다. 로딩 상태에서 Spinner를 표시하여 사용자 경험을 개선합니다.

Comment on lines 22 to 24
const setIdFunc = (id: string) => {
setId(id)
window.history.replaceState(null, '', `/categories/${id}`)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Next.js 라우터 사용을 고려하세요.

window.history.replaceState를 직접 사용하는 것은 Next.js의 클라이언트 사이드 라우팅을 우회합니다. 이는 프리페칭, 스크롤 복원 등의 Next.js 기능과 충돌할 수 있습니다.

다음 스크립트를 실행하여 Next.js router를 사용하는 다른 예시가 있는지 확인하세요:

next/navigationuseRouter를 사용하는 것이 더 적절할 수 있습니다:

import { useRouter } from 'next/navigation'

const router = useRouter()
const setIdFunc = (id: string) => {
  router.replace(`/categories/${id}`)
}

🏁 Script executed:

#!/bin/bash
# Next.js useRouter 사용 패턴 검색
rg -n "useRouter|router\.push|router\.replace" --type=tsx --type=ts -C 3

Length of output: 27


새로운 스크립트를 실행하여 코드베이스를 재검증하겠습니다.


---

🏁 Script executed:

```shell
#!/bin/bash
# TypeScript/TSX 파일에서 router 패턴 검색 (파일 타입 제약 제거)
rg -n "useRouter|router\.push|router\.replace|window\.history" -C 3 --glob="*.tsx" --glob="*.ts"

Length of output: 4609


🏁 Script executed:

#!/bin/bash
# 해당 파일 확인
fd "CategoryDetailPage.tsx" -type f

Length of output: 170


🏁 Script executed:

#!/bin/bash
# 파일 내용 확인
find . -name "CategoryDetailPage.tsx" -type f -exec cat {} \;

Length of output: 1746


Next.js 라우터 사용으로 변경하세요.

이 파일은 이미 'use client' 선언이 있고 next/navigation에서 usePathname을 임포트하고 있습니다. 코드베이스의 다른 컴포넌트들(HeaderHomeButton.tsx, HeaderBackButton.tsx 등)도 useRouter를 사용하여 네비게이션을 처리하고 있습니다.

window.history.replaceState를 직접 사용하면 Next.js의 클라이언트 사이드 라우팅 기능(프리페칭, 스크롤 복원 등)이 제대로 작동하지 않을 수 있습니다.

다음과 같이 수정하세요:

import { useRouter } from 'next/navigation'

const router = useRouter()
const setIdFunc = (id: string) => {
  router.replace(`/categories/${id}`)
}
🤖 Prompt for AI Agents
In apps/web/app/categories/[id]/CategoryDetailPage.tsx around lines 22 to 24,
replace the direct window.history.replaceState usage with Next.js client router
navigation: import and call useRouter from next/navigation, get router via
useRouter() in the component, and change setIdFunc to call
router.replace(`/categories/${id}`) so routing uses Next.js client-side
navigation and preserves prefetching/scroll restoration.

Comment on lines +26 to +28
useEffect(() => {
document.title = `공주대 맛집 | ${activeCategory?.name}`
}, [activeCategory])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

document.title 직접 조작을 제거하세요.

page.tsx에서 generateMetadata를 통해 메타데이터를 이미 관리하고 있으므로, 클라이언트 컴포넌트에서 document.title을 직접 설정하는 것은 불필요하며 Next.js의 메타데이터 시스템과 충돌할 수 있습니다.

다음과 같이 useEffect를 제거하세요:

-  useEffect(() => {
-    document.title = `공주대 맛집 | ${activeCategory?.name}`
-  }, [activeCategory])
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
document.title = `공주대 맛집 | ${activeCategory?.name}`
}, [activeCategory])
🤖 Prompt for AI Agents
In apps/web/app/categories/[id]/CategoryDetailPage.tsx around lines 26 to 28,
remove the client-side useEffect that sets document.title (document.title = `공주대
맛집 | ${activeCategory?.name}`) because page.tsx already manages metadata via
generateMetadata; delete the entire useEffect block and any imports or hooks
that are only used for that effect so the component relies on Next.js metadata
instead.

Comment on lines +7 to +27
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>
}): Promise<Metadata> {
const { id: categoryId } = await params

const data = await getCategories()
const categoryName = data?.find(({ id }) => id === categoryId)?.name

return {
title: `${categoryName}`,
description: `공주대학교 근처 ${categoryName} 맛집`,
openGraph: {
title: `공주대 맛집 | ${categoryName}`,
description: `공주대학교 근처 ${categoryName} 맛집`,
locale: 'ko-KR',
type: 'website',
},
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

generateMetadata에 에러 핸들링을 추가하세요.

카테고리를 찾지 못했을 때를 대비한 에러 핸들링이 필요합니다. categoryNameundefined일 경우 메타데이터에 "undefined"가 포함될 수 있습니다.

다음과 같이 폴백을 추가하세요:

 export async function generateMetadata({
   params,
 }: {
   params: Promise<{ id: string }>
 }): Promise<Metadata> {
   const { id: categoryId } = await params
 
   const data = await getCategories()
-  const categoryName = data?.find(({ id }) => id === categoryId)?.name
+  const categoryName = data?.find(({ id }) => id === categoryId)?.name || '카테고리'
 
   return {
     title: `${categoryName}`,
     description: `공주대학교 근처 ${categoryName} 맛집`,
     openGraph: {
       title: `공주대 맛집 | ${categoryName}`,
       description: `공주대학교 근처 ${categoryName} 맛집`,
       locale: 'ko-KR',
       type: 'website',
     },
   }
 }
🤖 Prompt for AI Agents
In apps/web/app/categories/[id]/page.tsx around lines 7 to 27, generateMetadata
currently assumes categoryName exists and may insert "undefined" into metadata;
update the function to handle missing categories by checking if categoryName is
undefined and either (a) use a safe fallback string (e.g., '카테고리' or 'Unknown')
when building title/description/openGraph, or (b) throw a notFound()/404 so the
page metadata isn't generated for a non-existent category; implement one of
these branches and ensure all metadata fields use the fallback/handled value.

Comment on lines +29 to 39
const Page = () => {
return (
<HydrationBoundaryPage
prefetch={async (queryClient) => {
await queryClient.prefetchQuery(useCategoryQueries.list())
}}
>
<CategoryDetailPage initId={id} />
<CategoryDetailPage />
</HydrationBoundaryPage>
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

HydrationBoundary를 사용하는 페이지에 dynamic 설정이 누락되었습니다.

PR 설명에 따르면 HydrationBoundary를 사용하는 페이지에 export const dynamic = 'force-dynamic'을 추가하여 빌드 타임 API 요청을 방지해야 합니다. 이 설정이 없으면 Next.js가 SSG로 빌드 시 MSW가 실행되지 않아 500 에러가 발생할 수 있습니다.

다음과 같이 dynamic export를 추가하세요:

 import type { Metadata } from 'next'
 import { HydrationBoundaryPage } from '@/HydrationBoundaryPage'
 import { useCategoryQueries } from '@/_apis/queries/category'
 import { CategoryDetailPage } from '@/categories/[id]/CategoryDetailPage'
 import { getCategories } from '@/_apis/services/category'
 
+export const dynamic = 'force-dynamic'
+
 export async function generateMetadata({

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/app/categories/[id]/page.tsx around lines 29 to 39, the page using
HydrationBoundaryPage is missing the Next.js dynamic export required to force
dynamic rendering; add a top-level export: export const dynamic =
'force-dynamic' in this file (placed at module scope, above the component or
exports) so build-time SSG is disabled and MSW-backed API calls run at request
time to avoid the 500 error.

Comment on lines +7 to +28
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>
}): Promise<Metadata> {
const { id } = await params

const data = await getPlaceDetail(id)
const { placeName } = data
const pageDescription = `공주대학교 근처 ${placeName} 맛집 상세 정보`

return {
title: `${placeName}`,
description: pageDescription,
openGraph: {
title: `공주대 맛집 | ${placeName}`,
description: pageDescription,
locale: 'ko-KR',
type: 'website',
},
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

메타데이터 생성이 올바르게 구현되었습니다.

generateMetadata에서 getPlaceDetail을 호출하고, Page 컴포넌트에서 usePlaceQueries.detail로 동일한 데이터를 프리페치하는 것이 중복처럼 보일 수 있으나, Next.js 15는 동일한 요청을 자동으로 중복 제거(dedupe)하므로 실제로는 한 번만 호출됩니다. 이는 Next.js의 권장 패턴입니다.

다만, export const dynamic = 'force-dynamic'이 이 파일에 없는데, HydrationBoundaryPage를 사용하는 다른 페이지들과 일관성을 위해 추가를 고려해보세요.

다음 스크립트로 HydrationBoundaryPage를 사용하면서 dynamic export가 없는 페이지를 확인하세요:


🏁 Script executed:

#!/bin/bash
# HydrationBoundaryPage를 사용하지만 dynamic export가 없는 파일 찾기
for file in $(rg -l "HydrationBoundaryPage" --type=tsx --type=ts apps/web/app); do
  if ! grep -q "export const dynamic" "$file"; then
    echo "Missing dynamic export: $file"
  fi
done

Length of output: 27


고마워요. 스크립트에 문제가 있네요. rg의 파일 타입을 수정하겠습니다.


---

🏁 Script executed:

```shell
#!/bin/bash
# tsx/ts 파일에서 HydrationBoundaryPage를 사용하는 모든 파일 찾기
# 그리고 export const dynamic 여부 확인

echo "=== Files using HydrationBoundaryPage ==="
rg "HydrationBoundaryPage" -t typescript apps/web/app --files-with-matches

echo ""
echo "=== Checking for dynamic export in places/[id]/page.tsx ==="
if grep -q "export const dynamic" apps/web/app/places/\[id\]/page.tsx; then
  echo "✓ Has dynamic export"
else
  echo "✗ Missing dynamic export"
fi

echo ""
echo "=== All HydrationBoundaryPage files with dynamic export status ==="
for file in $(rg "HydrationBoundaryPage" -t typescript apps/web/app --files-with-matches); do
  if grep -q "export const dynamic" "$file"; then
    echo "✓ $file"
  else
    echo "✗ $file"
  fi
done

Length of output: 265


파일 타입 지정 문제가 있네요. 다른 방식으로 접근하겠습니다.


🏁 Script executed:

#!/bin/bash
# 파일 타입 필터링 없이 HydrationBoundaryPage 검색
echo "=== Files using HydrationBoundaryPage ==="
rg "HydrationBoundaryPage" apps/web/app -l

echo ""
echo "=== Current file status ==="
echo "File: apps/web/app/places/[id]/page.tsx"
if grep -q "export const dynamic" "apps/web/app/places/[id]/page.tsx"; then
  echo "Dynamic export: ✓ Present"
else
  echo "Dynamic export: ✗ Missing"
fi

echo ""
echo "=== Checking all identified files for dynamic export ==="
rg "HydrationBoundaryPage" apps/web/app -l | while read file; do
  if grep -q "export const dynamic" "$file"; then
    echo "✓ HAS: $file"
  else
    echo "✗ LACKS: $file"
  fi
done

Length of output: 877


export const dynamic = 'force-dynamic' 추가 필요

검증 결과, apps/web/app/places/[id]/page.tsx는 실제로 export const dynamic 선언이 누락되어 있습니다. HydrationBoundaryPage를 사용하는 다른 페이지들을 확인한 결과:

  • 동적 선언 있음: page.tsx, requests/page.tsx, requests/[id]/page.tsx, likes/page.tsx, events/food-slot/page.tsx, events/lucky-draw/page.tsx (6개)
  • 동적 선언 없음: places/[id]/page.tsx, categories/[id]/page.tsx (2개)

대부분의 HydrationBoundaryPage 사용 페이지가 동적 선언을 포함하고 있으므로, 일관성 유지를 위해 export const dynamic = 'force-dynamic'을 이 파일에 추가하세요.

🤖 Prompt for AI Agents
In apps/web/app/places/[id]/page.tsx around lines 7 to 28, add the missing
export const dynamic = 'force-dynamic' declaration to match other
HydrationBoundaryPage files; insert this export near the top of the module
(before the generateMetadata export) so the page is treated as dynamic and
consistent with the other pages using hydration boundaries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 배포 관련 작업

2 participants