Back to Logs
Next.js 15App RouterFirebaseSuspenseMigrationTroubleshooting

CDN React → Next.js 15 App Router 마이그레이션 삽질기

2026. 4. 21.2분 읽기

CDN React → Next.js 15 마이그레이션 삽질기

프로젝트: TimeSlot Event OS 날짜: 2026-04-03 ~ 2026-04-05 배경: 5개 HTML 파일(6,135 LOC)을 Next.js 15 App Router로 전환


문제 1 — useSearchParams() 정적 빌드 오류

증상

Error: useSearchParams() should be wrapped in a suspense boundary at page "/sso"
  useSearchParams
    at SSOPage

npm run build/sso 페이지에서 정적 빌드 실패.

원인

Next.js 15에서 useSearchParams()는 동적 기능(Dynamic Feature)으로 분류된다. Suspense 경계 없이 사용하면 정적 생성(Static Generation) 단계에서 오류 발생.

해결

Suspense로 분리:

// Before — 빌드 실패
export default function SSOPage() {
  const params = useSearchParams(); // ❌
  ...
}

// After — 빌드 성공
export default function SSOPage() {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <SSOHandler />
    </Suspense>
  );
}

function SSOHandler() {
  const params = useSearchParams(); // ✅ Suspense 내부
  ...
}

규칙: useSearchParams, useRouter, usePathname 등 동적 훅은 Suspense 경계 안에서 사용.


문제 2 — 환경변수 late-stage discovery

증상

App Hosting 배포 후 일부 기능 작동 안 함. 확인해보니 GEMINI_API_KEY, BULK_SEND_URLapphosting.yaml에 누락.

원인

구현 시작 전 .env.example을 작성하지 않아서, 어떤 환경변수가 필요한지 전체 파악이 늦었음.

해결

apphosting.yaml에 누락된 환경변수 추가. 그리고 앞으로는 구현 전에 .env.example을 먼저 작성:

# .env.example
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
GEMINI_API_KEY=
BULK_SEND_URL=
REFIRED_NOTIFICATION_URL=

교훈: .env.example은 구현 이전 인프라 문서다. 먼저 작성하면 배포 단계에서 놀라지 않는다.


문제 3 — AdminApp.tsx가 1,955줄 모노리스가 된 이유

상황

어드민 대시보드를 단일 AdminApp.tsx에 7개 탭(대시보드, 스케줄, 체크인, AI 운영, SOS, 부스로그, 계정)을 모두 구현. 결과: 1,955 LOC.

원인

레거시 HTML에서 탭 전환이 JavaScript display: none/block 방식이었고, 이를 그대로 React state로 옮기다 보니 파일 분리 없이 한 컴포넌트에 누적.

Next.js는 탭이 아닌 라우트(/admin/schedule, /admin/checkin)가 자연스러운 패턴인데, 마이그레이션 속도 우선으로 구조 변경 없이 진행.

현재 대응

이 파일은 별도 PDCA 사이클(admin-refactor)로 분리 예정. 당장 기능적으로는 문제 없으므로 지금은 유지.

교훈: Next.js App Router 마이그레이션 시 탭 UI는 반드시 파일 기반 라우팅으로 설계해야 한다. 탭 = /admin/[tab] 구조. 나중에 쪼개면 state 추출, URL 구조 변경이 동시에 발생해서 더 어렵다.


결과 요약

문제교훈
useSearchParams 빌드 오류동적 훅은 Suspense 경계 필수
환경변수 late discovery.env.example을 구현 전에 작성
모노리틱 Admin 컴포넌트Next.js 탭 = 파일 기반 라우팅으로 처음부터 설계
HUNI² | Portfolio & Log