에브리서울 프론트엔드 애플리케이션입니다. React 19, Vite 8, TypeScript, Tailwind CSS v4, Zustand를 사용합니다.
pnpm install
copy .env.example .env
pnpm dev개발 서버의 기본 주소는 http://localhost:5173 입니다. Vite가 다른 포트를 사용 중이라고 판단하면 터미널에 표시되는 주소로 접속하면 됩니다.
로컬 개발 환경에서는 .env.example을 복사해 .env를 만든 뒤 값을 채웁니다.
| 이름 | 설명 | 기본 예시 |
|---|---|---|
VITE_API_BASE_URL |
프론트엔드가 호출할 백엔드 API 기본 주소 | http://localhost:8000/api |
VITE_GOOGLE_CLIENT_ID |
Google Identity Services 웹 클라이언트 ID | *.apps.googleusercontent.com |
VITE_VAPID_PUBLIC_KEY |
브라우저 Web Push 구독용 공개키 | B... |
프로덕션 Docker 컨테이너는 시작 시점에 런타임 설정을 /env.js로 생성합니다. 그래서 이미 빌드된 이미지도 API_BASE_URL, GOOGLE_CLIENT_ID, VAPID_PUBLIC_KEY 환경 변수만 바꿔 다른 환경에서 실행할 수 있습니다.
브라우저 푸시 알림은 VAPID 키가 필요합니다. 백엔드 저장소에서 다음 명령으로 키를 생성한 뒤, 출력값을 프론트엔드와 백엔드 환경 변수에 나눠 설정합니다.
python scripts/generate_vapid_keys.py프론트엔드에는 VITE_VAPID_PUBLIC_KEY, 백엔드에는 VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_CLAIM_EMAIL을 설정합니다. 프로덕션 Docker에서는 VAPID_PUBLIC_KEY가 웹 컨테이너 런타임 설정에도 전달됩니다.
pnpm dev # 개발 서버 실행
pnpm lint # ESLint 검사
pnpm typecheck # TypeScript 타입 검사
pnpm build # 타입 검사 후 프로덕션 빌드
pnpm check # lint + build 전체 검증
pnpm format # Prettier 포맷 적용
pnpm format:check # 포맷 검사src/App.tsx 앱 조립
src/components/layout 셸, 헤더, 사이드바 레이아웃
src/components/newsletter 뉴스레터 탭 UI
src/components/life-info 생활 정보 탭 UI
src/components/policy 정책 탭 UI
src/components/modals 모달 UI
src/components/shared 작은 재사용 UI
src/components/auth 인증 UI와 Google 로그인
src/auth 인증 연동 헬퍼
src/data 태그, 자치구 등 정적 선택지
src/hooks 상태를 가진 앱 훅
src/store Zustand 스토어
src/types 공용 타입
src/utils 순수 유틸리티
src/styles/index.css 전역 CSS와 Tailwind 진입점App.tsx는 앱 구성만 담당하도록 얇게 유지합니다. 새로운 동작은 역할에 맞게 컴포넌트, 훅, 스토어, 데이터, 유틸리티에 나눠 둡니다.
로컬 백엔드 저장소는 보통 이 저장소와 같은 상위 폴더 아래에 둡니다.
every-seoul
every-seoul-backend백엔드 API의 기본 로컬 주소는 http://localhost:8000/api 입니다. 프론트엔드에서 다른 주소를 호출해야 하면 .env의 VITE_API_BASE_URL 값을 변경합니다.
Google Cloud Console에서 OAuth 클라이언트를 만들 때 애플리케이션 유형은 웹 애플리케이션을 선택합니다.
로컬 개발용 승인된 JavaScript 원본:
http://localhost:5173
http://127.0.0.1:5173프로덕션 배포 시에는 실제 도메인도 추가합니다.
https://everyseoul.com같은 Google 클라이언트 ID를 프론트엔드와 백엔드 환경 변수에 설정합니다. 현재 프론트엔드는 화면 표시와 세션 상태를 위해 Google ID 토큰을 파싱합니다. 실제 프로덕션 인증에서는 이 ID 토큰을 백엔드로 보내 서버에서 검증한 뒤 세션을 생성해야 합니다.
프론트엔드 이미지는 정적 파일을 빌드한 뒤 Nginx로 제공합니다.
docker build -t every-seoul-web .compose.prod.yml은 Caddy, 프론트엔드, 백엔드, PostgreSQL을 함께 실행합니다. Caddy가 80, 443 포트를 열고 Let's Encrypt 인증서를 자동 발급합니다. 백엔드 저장소가 이 저장소와 같은 상위 폴더 아래에 있다고 가정합니다.
every-seoul
every-seoul-backend배포 환경 변수는 템플릿을 복사해 별도 파일로 관리합니다. 실제 .env.production은 Git에 커밋하지 않습니다.
cp .env.production.example .env.production.env.production에서 PUBLIC_WEB_HOST, PUBLIC_WEB_ORIGIN, POSTGRES_PASSWORD, GOOGLE_CLIENT_ID, ADMIN_API_KEY, SEOUL_OPEN_API_KEY, OPENROUTER_API_KEY를 실제 값으로 바꾼 뒤 실행합니다.
ADMIN_API_KEY는 뉴스레터 수집 파이프라인을 수동 실행할 때 쓰는 관리자 토큰입니다. 충분히 긴 임의 문자열로 설정하고 Git에 커밋하지 않습니다.
docker compose --env-file .env.production -f compose.prod.yml up --build -d
docker compose --env-file .env.production -f compose.prod.yml logs -f
docker compose --env-file .env.production -f compose.prod.yml downWindows PowerShell에서도 같은 Compose 파일을 사용할 수 있습니다.
Copy-Item .env.production.example .env.production
notepad .env.production
docker compose --env-file .env.production -f compose.prod.yml up --build -d
docker compose --env-file .env.production -f compose.prod.yml logs -f
docker compose --env-file .env.production -f compose.prod.yml down상태 확인:
curl http://localhost/health
curl http://localhost/api/health
curl https://everyseoul.com/health
curl https://everyseoul.com/api/healthPowerShell에서 상태를 확인할 때는 다음 명령을 사용할 수 있습니다.
Invoke-WebRequest http://localhost/health
Invoke-WebRequest http://localhost/api/health
Invoke-WebRequest https://everyseoul.com/health
Invoke-WebRequest https://everyseoul.com/api/health뉴스레터 탭의 "오늘의 강남구 요약"은 백엔드 DB에 저장된 뉴스레터를 표시합니다. 프론트엔드의 새로고침 버튼은 이미 저장된 최신 데이터를 다시 불러오며, 새 뉴스레터를 직접 생성하지는 않습니다.
백엔드는 ENABLE_SCHEDULER=true일 때 매일 04:00 KST에 서울 RSS, 문화행사, 생활 센서 데이터를 수집하고 자치구별 브리핑을 생성합니다.
배포 직후 바로 데이터를 채우고 싶다면 관리자 토큰으로 수동 실행합니다.
curl -X POST http://localhost/api/admin/pipeline/trigger \
-H "X-Admin-Token: $ADMIN_API_KEY"도메인으로 호출할 수도 있습니다.
curl -X POST https://everyseoul.com/api/admin/pipeline/trigger \
-H "X-Admin-Token: $ADMIN_API_KEY"PowerShell에서는 다음처럼 실행합니다.
Invoke-WebRequest `
-Method Post `
-Uri https://everyseoul.com/api/admin/pipeline/trigger `
-Headers @{"X-Admin-Token" = $env:ADMIN_API_KEY}실행 후 API 로그에서 진행 상황을 확인합니다.
docker compose --env-file .env.production -f compose.prod.yml logs -f api생성 결과는 다음처럼 확인할 수 있습니다.
curl "http://localhost/api/newsletters/today?district=강남구"- VPS, 클라우드 VM, Render, Fly.io, Railway 등 Docker 실행이 가능한 배포 대상을 정합니다.
- 실제 도메인을 서버에 연결합니다.
- 서버의 80/443 포트를 열고 도메인의 A 레코드가 서버를 가리키게 합니다. compose의 Caddy가 HTTPS를 자동 적용합니다.
- 프로덕션 도메인용 Google OAuth 웹 클라이언트 ID를 준비하거나 기존 클라이언트에 프로덕션 원본을 추가합니다.
.env.production을 만들고PUBLIC_WEB_HOST=everyseoul.com,PUBLIC_WEB_ORIGIN=https://everyseoul.com으로 설정합니다.API_BASE_URL을 프론트엔드가 호출할 API 경로로 설정합니다. 일반적으로/api를 사용합니다.POSTGRES_PASSWORD를 강한 고유 비밀번호로 바꿉니다.ADMIN_API_KEY를 강한 고유 토큰으로 바꿉니다.- 필요한 경우
SEOUL_OPEN_API_KEY,OPENROUTER_API_KEY를 실제 값으로 설정합니다. - 운영 환경에서 뉴스레터 수집을 자동 실행하려면
ENABLE_SCHEDULER=true로 설정합니다. - 초기 소규모 배포에서는
CREATE_DB_TABLES=true를 사용할 수 있지만, 운영이 안정되면 마이그레이션을 사용하고CREATE_DB_TABLES=false로 전환하는 편이 좋습니다. postgres_dataDocker 볼륨을 백업하거나 관리형 PostgreSQL 사용을 검토합니다.- 방화벽은 필요한 포트만 열어 둡니다. 이 compose 구성에서는 HTTP/HTTPS만 외부에 노출하고 PostgreSQL은 내부에 둡니다.
배포 전 검증 예시:
pnpm check
python -m compileall ..\every-seoul-backend\app
python -m pytest -s ..\every-seoul-backend\tests
docker compose --env-file .env.production -f compose.prod.yml config배포 후에는 실제 Google 계정으로 로그인, 로그아웃, 사용자 설정 저장, 북마크 저장/해제, 뉴스레터 수집 결과 표시가 정상 동작하는지 확인합니다.