324.ing

개발 노트

카메라 아카이브 사이트 324.ing의 개발 기록. 내가 쓴 카메라들을 정리하다가 만들기 시작한 개인 프로젝트.

2026-02-28

프로젝트 시작

  • 카메라 아카이브 프로젝트 초기 스캐폴딩
  • Next.js 14 App Router + TypeScript + Tailwind CSS 기반 설정
  • Prisma ORM + Supabase PostgreSQL 데이터베이스 연결
  • Cloudflare R2 파일 스토리지 연동 (presigned URL 방식)
  • JWT 기반 관리자 인증 시스템 구현
  • 핵심 스키마 설계: Camera, Photo, Project, SiteSettings 모델
  • Portfolio 시스템 구현: Work / Moment 두 가지 프로젝트 타입
  • ContentBlock 모델로 자유로운 콘텐츠 구성 (HEADING, TEXT, IMAGE, IMAGE_GRID, SLIDESHOW 등)
  • Vercel 빌드 시 prisma generate 자동 실행 설정
2026-03-11

디자인 & 테마

  • 화이트 미니멀 테마 적용 — 배경 #FAFAF8, 텍스트 계층화
  • 세리프 계열 헤딩 + 모노스페이스 메타데이터로 사진 아카이브 분위기 구성
  • Tailwind CSS 커스텀 컬러 토큰 정의 (text-primary, text-secondary, text-muted, accent, border 등)
  • 반응형 레이아웃: 모바일 햄버거 메뉴 포함 SiteNav 구현
2026-03-11

카메라 관리

  • 카메라 slug 수정 기능 추가 (ID 변경 시 관련 사진 자동 재연결)
  • 사용 기간 월 단위 입력 필드 추가 (monthStart, monthEnd)
  • 카메라 커버 이미지 업로드 기능 구현
  • 카메라 목록 yearStart, monthStart 기준 오름차순 정렬
2026-03-11

라이트박스

  • 라이트박스 오버레이 딤 조정 (50% → 75%), 외부 클릭 시 닫기
  • EXIF 값만 표시하도록 라이트박스 정보 패널 간소화
  • Work/Moment 프로젝트 상세 페이지에 라이트박스 추가
  • 공유 라이트박스 컴포넌트로 테마 통일 (BlockRenderer 내부 별도 구현 제거)
  • 라이트박스 하단 텍스트 색상 수정 — 어두운 배경 위 가독성 개선
  • EXIF 메타데이터 패널 가운데 정렬 + 공유 링크 하단 배치
2026-03-11

히어로 섹션

  • 홈 히어로: 16/9 비율 → 전체 화면 → nav 아래 뷰포트 높이로 순차 개선
  • 헤딩 오버레이 위치 좌하단 고정
  • Work/Moment 목록 페이지에 히어로 섹션 추가 (사진 + 문구)
  • 관리자 설정 페이지에서 Work/Moment 히어로 이미지 및 문구 편집 기능 구현
  • 히어로 문구에 Markdown 렌더링 적용 (react-markdown v10)
  • remark-breaks 플러그인 추가 — 단일 줄바꿈( )도 <br>로 변환
  • Prisma 스키마 마이그레이션: workHeroPhotoId, workHeroText, momentHeroPhotoId, momentHeroText 필드 추가
2026-03-11

내비게이션 & UX

  • 모든 페이지에 Breadcrumb 내비게이션 추가
  • 프로젝트 커버 이미지 픽커 구현 (Work/Moment)
  • 페이지 전환 시 상단 Progress Bar 추가 (NavigationProgress 컴포넌트)
  • 링크 클릭 감지 → pathname 변경 감지 → 완료 애니메이션 방식
  • 진행 바 색상: 사이트 accent(#7c5c3a), glow 효과 적용
2026-03-11

분석 코드 주입

  • 관리자 설정에 headerCode, bodyCode 필드 추가
  • layout.tsx에서 DB에서 코드를 읽어 <head>/<body>에 동적 삽입
  • <script> 태그 파싱하여 React 노드로 변환 (Server Component 호환)
  • 1시간 캐시 + 태그 기반 무효화로 성능 최적화
2026-03-11

보안 강화

  • 관리자 API 전체 인증 미들웨어 점검 및 누락 항목 수정
  • JWT 토큰 검증 강화, 쿠키 httpOnly/secure/sameSite 설정
  • SQL injection 방지 — Prisma ORM 파라미터 바인딩 일관 적용
  • XSS 방지 — dangerouslySetInnerHTML 사용 범위를 관리자 전용 코드 주입으로 제한
2026-03-11

성능 최적화

  • DB 인덱스 추가: isDeleted, takenAt, cameraId 복합 인덱스
  • 이미지 사이즈 최적화 및 Next.js Image 컴포넌트 sizes 속성 정교화
  • unstable_cache 도입 — 모든 공개 페이지 DB 쿼리 캐싱
  • 관리자 API에 revalidateTag 추가 — 데이터 변경 시 즉시 캐시 무효화
  • React.cache()로 getAdminSession 요청 내 중복 호출 제거
  • SiteNav를 root layout으로 이동 — 클라이언트 사이드 내비게이션 시 nav 재렌더링 방지
  • app/loading.tsx 추가 — 서버 렌더링 중 즉각적인 스켈레톤 피드백
  • ProjectCard, photoFilters 공통 모듈 추출 (코드 중복 제거)
2026-03-11

기타 개선

  • 비로그인 상태에서 Admin 링크를 메인 내비게이션에서 제거
  • 사이트 하단 Footer 추가 — 카피라이트 + 개발 노트 링크
  • 개발 노트 페이지 작성 (현재 페이지)
2026-03-13

포토북 라이브러리

  • Sanity CMS 연동 — 포토북 데이터 관리 (projectId: emspj2jw)
  • /photobooks 목록 페이지 + /photobooks/[slug] 상세 페이지 구현
  • 어드민 포토북 관리 페이지 추가 (/admin/photobooks) — Sanity CRUD
  • 포토북 카드 컴포넌트, 메타데이터 뷰어, 관련 도서 섹션 구현
  • 카테고리 필터 + 정렬 기능 (연도, 작가, 출판사)
  • 책 상세 페이지에서 관리자 로그인 상태 시 편집 버튼 노출
  • ?edit=slug 파라미터로 어드민 편집 모달 자동 열기
2026-03-13

업로드 & 스토리지 개선

  • ProjectUploadZone CORS 오류 수정 — presigned URL 방식 → 서버사이드 /api/admin/upload 방식으로 전환
  • 불필요한 /api/admin/upload/presign 엔드포인트 제거
  • 업로드 API 파일 크기 검증 추가 (사진 50MB, 커버 5MB)
  • EXIF JSON 파싱 try-catch 방어 로직 추가
2026-03-13

UI 개선

  • 라이트박스 닫기 버튼 간소화 — 원형 버튼 → 18px SVG X 아이콘
  • Work / Moment 상세 페이지 최대 너비 max-w-3xl → 1200px로 확장
  • toSlug 유틸 lib/slug.ts로 통합 — 한글 지원 버전으로 일원화
2026-03-13

보안 강화

  • middleware.ts — JWT_SECRET 미설정 시 즉시 401 반환 방어 로직 추가
  • layout.tsx bodyCode XSS 수정 — dangerouslySetInnerHTML 제거, parseHeadScripts() 안전 처리
  • 공개 API 라우트 전체 try-catch 추가 (cameras, photos, filters)
  • CLAUDE.md 작성 — 프로젝트 전체 구조 문서화 (라우트, 데이터 레이어, 업로드 플로우, 보안 정책)
2026-03-13

Google OAuth 어드민 로그인

  • 어드민 로그인 페이지에 Google 로그인 버튼 추가
  • 어드민 설정 페이지에 Google 계정 연동 / 해제 섹션 추가
  • /api/auth/google — OAuth 시작 라우트 (login / connect 두 가지 모드)
  • /api/auth/google/callback — 인가 코드 교환, 이메일 검증, JWT 쿠키 발급
  • SiteSettings.googleEmail 필드 추가 — 연동된 Google 이메일 저장
  • 등록된 이메일만 로그인 허용, 미등록 계정 접근 차단
2026-03-13

버그 수정 — unstable_cache Date 직렬화 오류

  • 증상: 5분 캐시 만료 후 Moment/Work 상세 페이지 접속 불가 (RSC 스트림 오류, digest 1830721307)
  • 원인: unstable_cache는 캐시 데이터를 JSON 직렬화하여 저장 → Date 객체가 ISO 문자열로 변환됨. 캐시 히트 시 string이 반환되는데 string에는 .toISOString()이 없어 TypeError 발생
  • 패턴: 첫 요청(캐시 미스) → DB에서 Date 객체 → 정상 동작 / 이후 요청(캐시 히트) → string 반환 → .toISOString() TypeError
  • 수정: photo.takenAt, project.publishedAt 등 Date 필드를 모두 new Date(value).toISOString() 방식으로 교체
  • 재발 방지: lib/dateUtils.ts에 safeISOString() 유틸리티 추가 — Date | string | null 모두 안전하게 처리. unstable_cache 사용 시 Date 필드는 반드시 이 함수를 통해 변환

Stack — Next.js 14 · Prisma · Supabase · Cloudflare R2 · Tailwind CSS · Vercel · Sanity CMS · Google OAuth