From c43100aef9fee9488820be1dcc34a146469e7bca Mon Sep 17 00:00:00 2001 From: MUsoftware Date: Thu, 21 May 2026 20:32:23 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20mkcert=20=EB=B0=8F=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=84=A4=EC=A0=95=EB=93=A4=EC=9D=84=20?= =?UTF-8?q?=EB=B2=94=EC=9A=A9=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=97=86=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/pyconkr-2025/src/main.tsx | 4 ++-- apps/pyconkr-2025/vite.config.mts | 21 ++++++++----------- apps/pyconkr-2026/src/main.tsx | 4 ++-- apps/pyconkr-2026/vite.config.mts | 21 ++++++++----------- apps/pyconkr-admin/src/main.tsx | 4 ++-- apps/pyconkr-admin/vite.config.ts | 21 ++++++++----------- apps/pyconkr-participant-portal/src/main.tsx | 4 ++-- .../vite.config.mts | 21 ++++++++----------- 8 files changed, 44 insertions(+), 56 deletions(-) diff --git a/apps/pyconkr-2025/src/main.tsx b/apps/pyconkr-2025/src/main.tsx index 9fde86e..3a008c4 100644 --- a/apps/pyconkr-2025/src/main.tsx +++ b/apps/pyconkr-2025/src/main.tsx @@ -45,8 +45,8 @@ const queryClient = new QueryClient({ }), }); -const backendApiDomainEnv: string = import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; -const backendApiDomain = backendApiDomainEnv.startsWith("http://") ? "" : backendApiDomainEnv; +// dev 서버에서는 vite proxy(/v1, /api)로 백엔드 호출 → relative URL 사용 (same-origin이라 CORS/쿠키 문제 회피) +const backendApiDomain = import.meta.env.DEV ? "" : import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; const CommonOptions: ContextOptions = { language: "ko", diff --git a/apps/pyconkr-2025/vite.config.mts b/apps/pyconkr-2025/vite.config.mts index 0026113..0b34481 100644 --- a/apps/pyconkr-2025/vite.config.mts +++ b/apps/pyconkr-2025/vite.config.mts @@ -10,14 +10,14 @@ import svgr from "vite-plugin-svgr"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, path.resolve(__dirname, "../../dotenv"), ""); const backendApiDomain = env.VITE_PYCONKR_BACKEND_API_DOMAIN ?? ""; - // 로컬 HTTP 백엔드면 http://localhost 로 서빙 + /v1, /api 를 proxy (mixed-content 회피 & CSRF 쿠키 동일 origin) - const isLocalHttpBackend = backendApiDomain.startsWith("http://"); - const host = isLocalHttpBackend ? "localhost" : "local.dev.pycon.kr"; + + // 백엔드 응답 쿠키의 Domain 속성(예: pycon.kr) 제거 — localhost origin에서 브라우저가 저장 가능하도록. + const proxyOptions = { target: backendApiDomain, changeOrigin: true, cookieDomainRewrite: "" }; return { base: "/", envDir: "../../dotenv", - plugins: [react(), mdx(), ...(isLocalHttpBackend ? [] : [mkcert({ hosts: [host] })]), svgr()], + plugins: [react(), mdx(), mkcert({ hosts: ["localhost"] }), svgr()], resolve: { alias: { "@frontend/common": path.resolve(__dirname, "../../packages/common/src"), @@ -26,14 +26,11 @@ export default defineConfig(({ mode }) => { }, }, server: { - host, - allowedHosts: [host], - proxy: isLocalHttpBackend - ? { - "/v1": { target: backendApiDomain, changeOrigin: true }, - "/api": { target: backendApiDomain, changeOrigin: true }, - } - : undefined, + host: "localhost", + proxy: { + "/v1": proxyOptions, + "/api": proxyOptions, + }, }, }; }); diff --git a/apps/pyconkr-2026/src/main.tsx b/apps/pyconkr-2026/src/main.tsx index e4c2632..1169837 100644 --- a/apps/pyconkr-2026/src/main.tsx +++ b/apps/pyconkr-2026/src/main.tsx @@ -45,8 +45,8 @@ const queryClient = new QueryClient({ }), }); -const backendApiDomainEnv: string = import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; -const backendApiDomain = backendApiDomainEnv.startsWith("http://") ? "" : backendApiDomainEnv; +// dev 서버에서는 vite proxy(/v1, /api)로 백엔드 호출 → relative URL 사용 (same-origin이라 CORS/쿠키 문제 회피) +const backendApiDomain = import.meta.env.DEV ? "" : import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; const CommonOptions: ContextOptions = { language: "ko", diff --git a/apps/pyconkr-2026/vite.config.mts b/apps/pyconkr-2026/vite.config.mts index 1f9cfbd..bf53c37 100644 --- a/apps/pyconkr-2026/vite.config.mts +++ b/apps/pyconkr-2026/vite.config.mts @@ -10,14 +10,14 @@ import svgr from "vite-plugin-svgr"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, path.resolve(__dirname, "../../dotenv"), ""); const backendApiDomain = env.VITE_PYCONKR_BACKEND_API_DOMAIN ?? ""; - // 로컬 HTTP 백엔드면 http://localhost 로 서빙 + /v1, /api 를 proxy (mixed-content 회피 & CSRF 쿠키 동일 origin) - const isLocalHttpBackend = backendApiDomain.startsWith("http://"); - const host = isLocalHttpBackend ? "localhost" : "local.dev.pycon.kr"; + + // 백엔드 응답 쿠키의 Domain 속성(예: pycon.kr) 제거 — localhost origin에서 브라우저가 저장 가능하도록. + const proxyOptions = { target: backendApiDomain, changeOrigin: true, cookieDomainRewrite: "" }; return { base: "/", envDir: "../../dotenv", - plugins: [react(), mdx(), ...(isLocalHttpBackend ? [] : [mkcert({ hosts: [host] })]), svgr()], + plugins: [react(), mdx(), mkcert({ hosts: ["localhost"] }), svgr()], resolve: { alias: { "@frontend/common": path.resolve(__dirname, "../../packages/common/src"), @@ -26,14 +26,11 @@ export default defineConfig(({ mode }) => { }, }, server: { - host, - allowedHosts: [host], - proxy: isLocalHttpBackend - ? { - "/v1": { target: backendApiDomain, changeOrigin: true }, - "/api": { target: backendApiDomain, changeOrigin: true }, - } - : undefined, + host: "localhost", + proxy: { + "/v1": proxyOptions, + "/api": proxyOptions, + }, }, }; }); diff --git a/apps/pyconkr-admin/src/main.tsx b/apps/pyconkr-admin/src/main.tsx index 118208c..c43c4cf 100644 --- a/apps/pyconkr-admin/src/main.tsx +++ b/apps/pyconkr-admin/src/main.tsx @@ -37,8 +37,8 @@ const queryClient = new QueryClient({ }), }); -const backendApiDomainEnv: string = import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; -const backendApiDomain = backendApiDomainEnv.startsWith("http://") ? "" : backendApiDomainEnv; +// dev 서버에서는 vite proxy(/v1, /api)로 백엔드 호출 → relative URL 사용 (same-origin이라 CORS/쿠키 문제 회피) +const backendApiDomain = import.meta.env.DEV ? "" : import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; const CommonOptions: ContextOptions = { debug: true, diff --git a/apps/pyconkr-admin/vite.config.ts b/apps/pyconkr-admin/vite.config.ts index ca277d0..8ed3b15 100644 --- a/apps/pyconkr-admin/vite.config.ts +++ b/apps/pyconkr-admin/vite.config.ts @@ -10,14 +10,14 @@ import svgr from "vite-plugin-svgr"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, path.resolve(__dirname, "../../dotenv"), ""); const backendApiDomain = env.VITE_PYCONKR_BACKEND_API_DOMAIN ?? ""; - // 로컬 HTTP 백엔드면 http://localhost 로 서빙 + /v1, /api 를 proxy (mixed-content 회피 & CSRF 쿠키 동일 origin) - const isLocalHttpBackend = backendApiDomain.startsWith("http://"); - const host = isLocalHttpBackend ? "localhost" : "local.dev.pycon.kr"; + + // 백엔드 응답 쿠키의 Domain 속성(예: pycon.kr) 제거 — localhost origin에서 브라우저가 저장 가능하도록. + const proxyOptions = { target: backendApiDomain, changeOrigin: true, cookieDomainRewrite: "" }; return { base: "/", envDir: "../../dotenv", - plugins: [react(), mdx(), ...(isLocalHttpBackend ? [] : [mkcert({ hosts: [host] })]), svgr()], + plugins: [react(), mdx(), mkcert({ hosts: ["localhost"] }), svgr()], resolve: { alias: { "@frontend/common": path.resolve(__dirname, "../../packages/common/src"), @@ -26,14 +26,11 @@ export default defineConfig(({ mode }) => { }, }, server: { - host, - allowedHosts: [host], - proxy: isLocalHttpBackend - ? { - "/v1": { target: backendApiDomain, changeOrigin: true }, - "/api": { target: backendApiDomain, changeOrigin: true }, - } - : undefined, + host: "localhost", + proxy: { + "/v1": proxyOptions, + "/api": proxyOptions, + }, }, }; }); diff --git a/apps/pyconkr-participant-portal/src/main.tsx b/apps/pyconkr-participant-portal/src/main.tsx index 6018a27..9c69c0c 100644 --- a/apps/pyconkr-participant-portal/src/main.tsx +++ b/apps/pyconkr-participant-portal/src/main.tsx @@ -35,8 +35,8 @@ const queryClient = new QueryClient({ const muiTheme = createTheme(); -const backendApiDomainEnv: string = import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; -const backendApiDomain = backendApiDomainEnv.startsWith("http://") ? "" : backendApiDomainEnv; +// dev 서버에서는 vite proxy(/v1, /api)로 백엔드 호출 → relative URL 사용 (same-origin이라 CORS/쿠키 문제 회피) +const backendApiDomain = import.meta.env.DEV ? "" : import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN; const CommonOptions: ContextOptions = { language: "ko", diff --git a/apps/pyconkr-participant-portal/vite.config.mts b/apps/pyconkr-participant-portal/vite.config.mts index 86e61cb..dd34d43 100644 --- a/apps/pyconkr-participant-portal/vite.config.mts +++ b/apps/pyconkr-participant-portal/vite.config.mts @@ -10,14 +10,14 @@ import svgr from "vite-plugin-svgr"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, path.resolve(__dirname, "../../dotenv"), ""); const backendApiDomain = env.VITE_PYCONKR_BACKEND_API_DOMAIN ?? ""; - // 로컬 HTTP 백엔드면 http://localhost 로 서빙 + /v1, /api 를 proxy (mixed-content 회피 & CSRF 쿠키 동일 origin) - const isLocalHttpBackend = backendApiDomain.startsWith("http://"); - const host = isLocalHttpBackend ? "localhost" : "local.dev.pycon.kr"; + + // 백엔드 응답 쿠키의 Domain 속성(예: pycon.kr) 제거 — localhost origin에서 브라우저가 저장 가능하도록. + const proxyOptions = { target: backendApiDomain, changeOrigin: true, cookieDomainRewrite: "" }; return { base: "/", envDir: "../../dotenv", - plugins: [react(), mdx(), ...(isLocalHttpBackend ? [] : [mkcert({ hosts: [host] })]), svgr()], + plugins: [react(), mdx(), mkcert({ hosts: ["localhost"] }), svgr()], resolve: { alias: { "@frontend/common": path.resolve(__dirname, "../../packages/common/src"), @@ -26,14 +26,11 @@ export default defineConfig(({ mode }) => { }, }, server: { - host, - allowedHosts: [host], - proxy: isLocalHttpBackend - ? { - "/v1": { target: backendApiDomain, changeOrigin: true }, - "/api": { target: backendApiDomain, changeOrigin: true }, - } - : undefined, + host: "localhost", + proxy: { + "/v1": proxyOptions, + "/api": proxyOptions, + }, }, }; }); From e2dd339304e9671be289f5562f845376a389b630 Mon Sep 17 00:00:00 2001 From: MUsoftware Date: Thu, 21 May 2026 20:43:28 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20Vite=20proxyOptions=EC=97=90=20X-F?= =?UTF-8?q?rontend-Domain=20=ED=97=A4=EB=8D=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/pyconkr-2025/vite.config.mts | 7 ++++++- apps/pyconkr-2026/vite.config.mts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/pyconkr-2025/vite.config.mts b/apps/pyconkr-2025/vite.config.mts index 0b34481..767b50f 100644 --- a/apps/pyconkr-2025/vite.config.mts +++ b/apps/pyconkr-2025/vite.config.mts @@ -12,7 +12,12 @@ export default defineConfig(({ mode }) => { const backendApiDomain = env.VITE_PYCONKR_BACKEND_API_DOMAIN ?? ""; // 백엔드 응답 쿠키의 Domain 속성(예: pycon.kr) 제거 — localhost origin에서 브라우저가 저장 가능하도록. - const proxyOptions = { target: backendApiDomain, changeOrigin: true, cookieDomainRewrite: "" }; + const proxyOptions = { + target: backendApiDomain, + changeOrigin: true, + cookieDomainRewrite: "", + headers: { "X-Frontend-Domain": "2025.pycon.kr" }, + }; return { base: "/", diff --git a/apps/pyconkr-2026/vite.config.mts b/apps/pyconkr-2026/vite.config.mts index bf53c37..7f841e3 100644 --- a/apps/pyconkr-2026/vite.config.mts +++ b/apps/pyconkr-2026/vite.config.mts @@ -12,7 +12,12 @@ export default defineConfig(({ mode }) => { const backendApiDomain = env.VITE_PYCONKR_BACKEND_API_DOMAIN ?? ""; // 백엔드 응답 쿠키의 Domain 속성(예: pycon.kr) 제거 — localhost origin에서 브라우저가 저장 가능하도록. - const proxyOptions = { target: backendApiDomain, changeOrigin: true, cookieDomainRewrite: "" }; + const proxyOptions = { + target: backendApiDomain, + changeOrigin: true, + cookieDomainRewrite: "", + headers: { "X-Frontend-Domain": "2026.pycon.kr" }, + }; return { base: "/", From c4ccdd97185c7bef713008a59822d686c06f0cf4 Mon Sep 17 00:00:00 2001 From: MUsoftware Date: Thu, 21 May 2026 21:01:08 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20README.md=20=EC=9E=AC=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 125 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 40ede56..e023324 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,77 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default tseslint.config({ - extends: [ - // Remove ...tseslint.configs.recommended and replace with this - ...tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - ...tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - ...tseslint.configs.stylisticTypeChecked, - ], - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) +# PyCon Korea Frontend (2025 - ) + +2025년 이후부터 PyCon Korea 사이트들의 모노레포입니다. + +## 구성 + +- `apps/pyconkr-2025` — 2025 행사 사이트 +- `apps/pyconkr-2026` — 2026 행사 사이트 +- `apps/pyconkr-admin` — 관리자 페이지 +- `apps/pyconkr-participant-portal` — 참가자 포털 +- `packages/common`, `packages/shop` — 앱들이 공유하는 코드 +- `dotenv/` — 모든 앱이 공유하는 환경변수 파일 + +## 요구 사항 + +- Node.js 22+ +- pnpm (`package.json`의 `packageManager` 필드 버전. `corepack enable`로 자동 설치 가능) +- 첫 실행 시 mkcert가 로컬 CA를 시스템 신뢰 저장소에 자동 설치합니다 (관리자 비밀번호 요구될 수 있음) + +## 설치 + +```bash +pnpm install ``` -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default tseslint.config({ - plugins: { - // Add the react-x and react-dom plugins - 'react-x': reactX, - 'react-dom': reactDom, - }, - rules: { - // other rules... - // Enable its recommended typescript rules - ...reactX.configs['recommended-typescript'].rules, - ...reactDom.configs.recommended.rules, - }, -}) +## 환경변수 + +기본값은 `dotenv/.env.development`(원격 dev 백엔드)와 `dotenv/.env.production`에 들어 있고, 그대로 둬도 dev는 동작합니다. + +| 키 | 설명 | +|---|---| +| `VITE_PYCONKR_BACKEND_API_DOMAIN` | 백엔드 API 도메인. dev 서버에서는 vite proxy(`/v1`, `/api`)의 target으로 쓰이고, prod 빌드에서는 브라우저가 직접 호출합니다. | +| `VITE_PYCONKR_BACKEND_CSRF_COOKIE_NAME` | 백엔드 CSRF 토큰 쿠키 이름. 환경(prod / dev / local)별로 prefix가 달라서 분리되어 있습니다. | +| `VITE_PYCONKR_FRONTEND_DOMAIN` | 프론트엔드의 외부 도메인. 관리자 페이지의 외부 링크 생성 등에 사용됩니다. | +| `VITE_PYCONKR_SHOP_IMP_ACCOUNT_ID` | PortOne(아임포트) 가맹점 식별자. 결제 모듈 초기화에 사용됩니다. | + +로컬에서 값을 덮어쓰고 싶다면 **`dotenv/.env.development.local`**(gitignored)을 만들어 사용하세요. 예: 로컬에서 직접 띄운 백엔드를 쓰고 싶을 때 — + +```bash +VITE_PYCONKR_BACKEND_API_DOMAIN=http://localhost:8000 +VITE_PYCONKR_BACKEND_CSRF_COOKIE_NAME=LOCAL_PYCONKR_BACKEND_csrftoken ``` + +## 개발 서버 실행 + +각 앱은 `https://localhost:`로 뜹니다. 포트는 vite가 비어 있는 것을 자동 선택 (보통 5173부터). + +```bash +pnpm dev:@apps/pyconkr-2025 +pnpm dev:@apps/pyconkr-2026 +pnpm dev:@apps/pyconkr-admin +pnpm dev:@apps/pyconkr-participant-portal +``` + +백엔드 호출은 vite proxy(`/v1`, `/api`)로 forward되므로 별도 `/etc/hosts` 설정은 필요 없습니다. CORS와 쿠키(`Secure`, `Domain` 속성)도 proxy 단에서 자동으로 처리합니다. + +## 빌드 / 프리뷰 + +```bash +pnpm build:@apps/pyconkr-2025 +pnpm preview:@apps/pyconkr-2025 +``` + +다른 앱도 동일한 패턴 (`build:@apps/`, `preview:@apps/`). + +## 린트 / 포맷 + +```bash +pnpm lint +pnpm format # 자동 수정 +pnpm format:check # 검사만 +``` + +## 자주 마주치는 이슈 + +- **TS 에러가 IDE에 떠 있는데 빌드/dev는 잘 됨**: 패키지 버전 변경 후 `node_modules/.pnpm/`에 orphan이 남아 IDE TS 서비스가 헷갈리는 경우입니다. `rm -rf node_modules && pnpm install` 후 VS Code의 TypeScript 서버를 재시작하세요. +- **mkcert 인증서 오류**: `mkcert -install`을 한 번 직접 실행해보세요.