Unsplash 스타일의 개인 사진 블로그 MVP입니다. 메인 화면에서 아래로 스크롤할수록 사진이 이어지고, 각 사진을 눌러 상세 페이지를 볼 수 있습니다.
- Next.js (App Router)
- TypeScript
- Tailwind CSS
- Vercel 배포 최적화 구조
- Supabase (Postgres + Storage) 마이그레이션 준비
npm install
npm run dev브라우저에서 http://localhost:3000을 열면 됩니다.
public/photos: 실제 사진 파일supabase/migrations/0001_create_photos.sql: Supabase 스키마/RLS/Storage 정책src/app/api/photos/route.ts: 무한 스크롤용 페이지네이션 APIsrc/app/page.tsx: 메인 Masonry 피드src/app/photo/[slug]/page.tsx: 사진 상세 페이지
- Supabase Storage
photos버킷에 이미지 업로드 - Supabase
public.photos에 메타데이터 insert/upsert
- GitHub에 푸시
- Vercel에서 저장소 Import
- Framework Preset
Next.js확인 후 Deploy
별도 환경변수 없이 바로 배포됩니다.
- 모든 실행/작업마다 학습 복습용 기록을 반드시 갱신합니다.
- 기록 파일:
docs/execution-log.md - 규칙 원문:
AGENTS.md
- 멀티 에이전트 작업은
tmux세션에서 시작하는 것을 기본으로 합니다. - 권장 3분할:
- Pane 1: Orchestrator (계획/통합/최종 검증)
- Pane 2: Explorer (코드 탐색/영향 범위 분석)
- Pane 3: Worker (구현/테스트)
- 에이전트 간 파일 소유 범위를 분리해 충돌을 줄이고, 최종 검증은 Orchestrator pane에서 수행합니다.
상세 문서:
docs/supabase-phase1.md
환경변수:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYSUPABASE_SERVICE_ROLE_KEYADMIN_ALLOWED_EMAILS(쉼표 구분 관리자 이메일 allowlist, 예:admin@site.com,owner@site.com)ADMIN_UPLOAD_TOKEN(선택: 레거시 토큰 값)ADMIN_UPLOAD_LEGACY_TOKEN_ENABLED(선택:true일 때만 레거시 토큰 fallback 허용)ADMIN_UPLOAD_MAX_FILE_SIZE_BYTES(선택: 업로드 최대 파일 크기 바이트, 기본26214400= 25MB)OPENAI_API_KEY(관리자 업로드 AI 메타데이터 추천 API)OPENAI_VISION_MODEL(선택: 기본값gpt-4.1-mini)
실행 순서:
- Supabase SQL Editor에서
supabase/migrations/0001_create_photos.sql실행 - Supabase SQL Editor에서
supabase/migrations/0002_add_photo_exif_columns.sql실행 (EXIF 컬럼) - Supabase SQL Editor에서
supabase/migrations/0003_make_taken_at_nullable.sql실행 - Supabase SQL Editor에서
supabase/migrations/0004_add_photo_exif_lens_iso_columns.sql실행 (렌즈/ISO 컬럼) - (초기 1회) 메타데이터 import (
data/photos.json이 있을 때):
npm run supabase:import:photos- 스토리지 업로드 + DB
srcURL 동기화:
npm run supabase:sync:storage- 경로:
/admin/upload - 동작:
- 이미지 파일 업로드
- 업로드 전/중에 AI 비전 모델로 제목/캡션/태그 추천 (수동 수정 가능)
photos버킷 저장public.photos레코드 자동 생성- 최소 해상도 정책 자동 적용:
short side >= 2000andlong side >= 3000 - 기준 미달 이미지는 자동 업스케일 후 WebP로 저장
- EXIF 자동 추출(가능한 항목) + 수동 수정 입력 후 저장
- 보안:
- Supabase Auth 로그인 후 업로드 가능
- API는 Bearer access token 검증 +
ADMIN_ALLOWED_EMAILSallowlist 검사 - 레거시 토큰 방식은
ADMIN_UPLOAD_LEGACY_TOKEN_ENABLED=true일 때만 fallback 허용(기본 비활성) - 업로드 파일은 MIME 타입/최대 파일 크기 검증 후 처리
- 다운로드 API는 Supabase Storage의 신뢰된 URL만 허용해 서버 측 임의 URL fetch를 차단
- 메인(
/) 우측 상단:- 비로그인:
GitHub 로그인버튼 표시 - 관리자 로그인:
Upload,Manage,로그아웃버튼 표시
- 비로그인:
- 관리자 목록:
- 경로:
/admin/photos - 기능: 사진 목록 확인 + 상세 페이지 이동
- 경로:
- 상세 관리자 액션:
- 경로:
/photo/[slug] - 관리자에게만
Edit,Delete버튼 노출 Edit: title/caption/tags/takenAt/slug 수정 가능- slug 변경 저장 시 새 상세 URL로 즉시 이동
Delete:DELETE확인 입력 후 photos 레코드 + storage 객체 즉시 삭제
- 경로:
- Supabase
Authentication > Providers > GitHub를 활성화한다. - GitHub
Settings > Developer settings > OAuth Apps에서 OAuth App을 만든다. - GitHub OAuth App의 Callback URL을 다음으로 설정한다:
https://<SUPABASE_PROJECT_REF>.supabase.co/auth/v1/callback
- GitHub에서 발급된
Client ID,Client Secret을 Supabase GitHub Provider에 입력한다. - Supabase
Authentication > URL Configuration에 redirect URL을 추가한다:- 로컬:
http://localhost:3000/admin/upload - 프로덕션:
https://<your-domain>/admin/upload
- 로컬:
- 프로젝트 환경변수
ADMIN_ALLOWED_EMAILS에 업로드를 허용할 GitHub 계정 이메일을 등록한다.
- 업로드 이미지는 WebP로 변환됩니다.
- 다운로드 이미지는 AVIF로 변환됩니다.
- 다운로드 이미지는 서버에서 재인코딩되어 EXIF/메타데이터가 제거됩니다.
- 데스크탑 품질 기준:
short side >= 2000long side >= 3000
기존 데이터 즉시 백필:
npm run supabase:backfill:min-resolution직접 모드 지정:
node --env-file=.env.local scripts/supabase/backfill-min-resolution.mjs --mode=replace
node --env-file=.env.local scripts/supabase/backfill-min-resolution.mjs --mode=versioned이 환경에서는 전역 vercel 대신 npx vercel 사용:
npx vercel whoami
npx vercel env ls환경변수 추가:
printf '%s\n' "$ADMIN_ALLOWED_EMAILS" | npx vercel env add ADMIN_ALLOWED_EMAILS development
printf '%s\n' "$ADMIN_ALLOWED_EMAILS" | npx vercel env add ADMIN_ALLOWED_EMAILS preview
printf '%s\n' "$ADMIN_ALLOWED_EMAILS" | npx vercel env add ADMIN_ALLOWED_EMAILS production배포:
npx vercel --prod- 로컬 사진 기반 Masonry 피드
- 사진 상세 페이지
- Hover UI(설명, 하트, 다운로드)
- 사진 업로드/동기화 시 EXIF 메타데이터 추출
- 촬영 시각, 카메라 정보, 렌즈 정보, GPS 좌표
- GPS 좌표를 장소명(도시/스팟)으로 역지오코딩
- 이미지 자체를 AI 비전 모델로 분석해 장면/피사체 태그 생성
- EXIF/GPS + 비전 분석 결과를 합쳐 캡션/태그 자동 생성
- 생성 결과를 Supabase
public.photos에 반영하고 수동 수정 가능하게 유지
권장 파이프라인 출력 필드:
location:country,city,spotexif:camera,lens,focalLength,aperture,shutterSpeed,isoai:captionDraft,tagsDraft,confidence