refactor: 프론트·API 를 같은 도메인(/api)으로 통합
브라우저가 프론트와 API 를 서로 다른 도메인으로 호출하던 구조를 같은 도메인의 /api 경로로 통합한다. 운영은 Nginx Proxy Manager 의 Proxy Host(/ → frontend:8080)와 Custom location(/api → backend:3000)이, 개발은 vite dev 프록시가 같은 규칙으로 백엔드에 넘긴다. - 같은 출처 호출이라 CORS 설정이 필요 없고 CSP connect-src 'self' 가 그대로 동작한다 (기존 예시대로 별도 도메인에 배포하면 CSP 에 막혀 API 호출이 실패했다) - 프론트 번들의 baseURL 이 /api 상대경로라 도메인이 바뀌어도 다시 빌드할 필요가 없다 - NPM 설정 절차와 주의점(Cache Assets 끄기, proxy 네트워크 생성)을 README "운영 배포"에 정리했다 - 프록시 설정이 빠지면 frontend nginx 가 /api 를 404 로 막는다 — index.html(200)로 조용히 실패하지 않게 함께 고친 CORS 문제: - CORS_ORIGIN 에 콤마로 여러 오리진을 적으면 문자열 전체를 하나로 비교해 모두 막히던 버그 - 운영에서도 localhost:5173·localhost:3000 이 credentials 포함으로 항상 허용되던 문제 - 형식이 잘못된 오리진(경로 포함 등)은 기동 시점에 실패하도록 검증 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ description: Vue 3 프론트엔드(frontend/) 개발 규칙 — Composition API
|
||||
|
||||
| 제공 항목 | 위치 | 비고 |
|
||||
| --- | --- | --- |
|
||||
| 중앙 Axios 인스턴스 | `src/composables/useApi.ts` | baseURL=`VITE_API_BASE_URL`, `withCredentials` |
|
||||
| 중앙 Axios 인스턴스 | `src/composables/useApi.ts` | baseURL=`VITE_API_BASE_URL`(비면 같은 도메인의 `/api`), `withCredentials` |
|
||||
| 응답 언래핑 인터셉터 | `useApi.ts` | `{success,data}→data`, 에러 코드 정규화(`ApiError`), 중복 알림 억제 |
|
||||
| 에러 코드 사전 | `src/shared/constants/error-codes.generated.ts` | **자동 생성 파일 — 직접 수정 금지** ([[error-codes]]) |
|
||||
| 세션 만료 처리 | `useApi.ts` 의 `toAuthCode()` | 로그인 상태에서 401 → `AUTH_002` 승격 + `authStore.clearAuth()` |
|
||||
@@ -27,7 +27,7 @@ description: Vue 3 프론트엔드(frontend/) 개발 규칙 — Composition API
|
||||
| 공통 컴포넌트 예제 | `src/components/BaseButton.vue` | 재사용 컴포넌트 기준 패턴(타입 Props/Emits·scoped·rem·a11y) |
|
||||
| 라우터 | `src/router/index.ts` | lazy import + `auth.store` 연동 `requiresAuth` 가드 |
|
||||
| env 타입 선언 | `frontend/env.d.ts` | `VITE_API_BASE_URL` |
|
||||
| dev API 프록시 | `vite.config.ts` 의 `server.proxy` | `/api` → 백엔드. 없으면 vite 가 SPA 폴백으로 **HTML 을 200 으로** 돌려줘 조용히 실패한다 |
|
||||
| dev API 프록시 | `vite.config.ts` 의 `server.proxy` | `/api` → 백엔드 (운영 NPM Custom location 과 같은 역할). 없으면 vite 가 SPA 폴백으로 **HTML 을 200 으로** 돌려줘 조용히 실패한다 |
|
||||
| dev 서버 포트 | `vite.config.ts` 의 `loadEnv` | 루트 `.env.{mode}` 의 `FRONTEND_PORT`/`BACKEND_PORT` 를 읽는다. `strictPort` 로 포트 충돌 시 fail-fast ([[env-structure]]) |
|
||||
|
||||
> **개발 서버는 호스트에서 띄운다** — `cd frontend && npm run dev` (→ `http://localhost:${FRONTEND_PORT}`).
|
||||
@@ -123,7 +123,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
```typescript
|
||||
// composables/useApi.ts — 중앙 Axios 인스턴스 (구조 요약)
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // env.d.ts 에 타입 선언
|
||||
// 비어 있으면 같은 도메인의 /api — 운영은 NPM Custom location, 개발은 vite 프록시가 백엔드로 넘긴다
|
||||
baseURL: resolveApiBaseUrl(import.meta.env.VITE_API_BASE_URL),
|
||||
withCredentials: true, // HttpOnly 쿠키 전송
|
||||
})
|
||||
|
||||
@@ -218,11 +219,12 @@ router.beforeEach((to) => {
|
||||
- [ ] **목록 화면**: 응답은 `{items, pageInfo}` — `shared/types/pagination.ts` 타입 사용.
|
||||
- [ ] **컴포넌트**: `<script setup lang="ts">` + `<style scoped>` + rem 단위. 인라인 스타일·Options API 금지.
|
||||
- [ ] **타입**: Props/Emits 는 인터페이스로 정의.
|
||||
- [ ] **라우트**: 페이지는 lazy import, 보호 라우트는 `meta.requiresAuth`.
|
||||
- [ ] **라우트**: 페이지는 lazy import, 보호 라우트는 `meta.requiresAuth`. `/api` 로 시작하는 경로는 쓰지 않는다 (백엔드로 간다 — [[api-design]]).
|
||||
- [ ] **auth 가드**: `router/index.ts` 가드는 `auth.store.ts` 의 `isLoggedIn` 과 이미 연결되어 있다. 로그인 페이지 추가 시 리다이렉트 대상을 `{ name: 'login' }` 으로 바꾼다.
|
||||
- [ ] **lint/test**: 커밋 전 `npm run lint` (`--max-warnings 0`) + `npm run type-check` + `npm run test:unit` 통과. 자동 수정은 `npm run lint:fix`.
|
||||
- [ ] **린트로 강제되는 항목**: Options API·인라인 스타일·`console`·`any` 는 빌드가 실패한다.
|
||||
- [ ] **env**: `VITE_API_BASE_URL` 은 빌드 시 루트 `BACKEND_API_URL` 에서 주입된다 — `frontend/.env` 에 별도 작성 불필요.
|
||||
- [ ] **env**: `VITE_API_BASE_URL` 은 빌드 시 루트 `BACKEND_API_URL`(기본 `/api` — 같은 도메인)에서 주입된다 — `frontend/.env` 에 별도 작성 불필요.
|
||||
절대 URL 로 바꾸면 다른 오리진 호출이 되므로 백엔드 `CORS_ORIGIN` 과 `nginx.conf` 의 CSP `connect-src` 도 함께 바꿔야 한다.
|
||||
- [ ] **dev 서버**: 프론트엔드는 호스트에서 `npm run dev` 로 띄운다 (컨테이너는 `--profile frontend` 선택, [[docker]]).
|
||||
- [ ] **포트**: `vite.config.ts` 에 포트를 새로 하드코딩하지 않는다 — 루트 `.env.{mode}` 의 `FRONTEND_PORT`/`BACKEND_PORT` 가 SSOT ([[env-structure]]).
|
||||
- [ ] **dev 에서 API 가 안 될 때**: 응답이 JSON 이 아니라 HTML 이면 vite 프록시(`server.proxy`) 문제다.
|
||||
|
||||
Reference in New Issue
Block a user