Frontend Framework: React 19.1.1 + TypeScript 5.9.3
Build Tool: Vite 7.1.7
Routing: React Router DOM 7.9.4
Backend: Firebase (Authentication + Firestore)
Internationalization: i18next + react-i18next
Styling: Bootstrap 5 (via CDN) + CSS
State Management: React Context API (Theme)todo-app/
├── public/ # 정적 파일
│ └── vite.svg
├── src/
│ ├── components/ # React 컴포넌트
│ │ ├── AddTodoForm.tsx # Todo 추가 폼
│ │ ├── TodoItem.tsx # Todo 단일 아이템
│ │ └── TodoList.tsx # Todo 목록 컨테이너
│ ├── context/ # React Context
│ │ └── ThemeContext.tsx # 다크모드 테마 관리
│ ├── firebase/ # Firebase 설정 및 서비스
│ │ ├── config.ts # Firebase 초기화
│ │ └── services.ts # Firestore CRUD 함수
│ ├── hooks/ # Custom React Hooks
│ │ ├── useAuth.ts # 인증 상태 관리
│ │ └── useTodos.ts # Todo 목록 관리
│ ├── locales/ # 다국어 번역 파일
│ │ ├── en/translation.json # 영어
│ │ ├── ko/translation.json # 한국어
│ │ └── ja/translation.json # 일본어
│ ├── pages/ # 페이지 컴포넌트
│ │ ├── HomePage.tsx # 메인 페이지 (Todo 관리)
│ │ └── LoginPage.tsx # 로그인/회원가입 페이지
│ ├── App.tsx # 앱 라우팅
│ ├── main.tsx # 앱 진입점
│ ├── i18n.ts # i18next 설정
│ ├── App.css # 앱 스타일
│ └── index.css # 전역 스타일
├── firestore.rules # Firestore 보안 규칙
├── firestore.indexes.json # Firestore 인덱스
├── firebase.json # Firebase 설정
├── .firebaserc # Firebase 프로젝트 ID
├── package.json # 의존성 관리
├── tsconfig.json # TypeScript 설정
└── vite.config.ts # Vite 설정{
"dependencies": {
"firebase": "^12.4.0",
"i18next": "^25.6.0",
"i18next-browser-languagedetector": "^8.2.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-i18next": "^16.1.0",
"react-router-dom": "^7.9.4"
},
"devDependencies": {
"@types/node": "^24.6.0",
"@types/react": "^19.1.16",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.4",
"firebase-tools": "^14.20.0",
"typescript": "~5.9.3",
"vite": "^7.1.7"
}
}App (라우팅)
├── LoginPage (비인증)
│ └── 로그인/회원가입 폼
└── HomePage (인증 후)
├── Header
│ ├── 제목
│ ├── 언어 선택기
│ ├── 테마 토글 버튼
│ └── 로그아웃 버튼
├── AddTodoForm
│ ├── 텍스트 입력
│ └── 추가 버튼
└── TodoList
└── TodoItem[] (반복)
├── 체크박스
├── 텍스트
└── 삭제 버튼┌─────────────────────────────────────────────────────────────┐
│ Firebase Cloud │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ Authentication │ │ Firestore DB │ │
│ │ (Auth SDK) │ │ (todos 컬렉션) │ │
│ └────────────────┘ └─────────────────┘ │
└──────────┬──────────────────────────────┬──────────────────┘
│ │
│ onAuthStateChanged │ onSnapshot (실시간)
│ │
┌──────────▼──────────────────────────────▼──────────────────┐
│ Frontend (React) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ main.tsx (전역 AbortError 차단 시스템) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ App.tsx (React Router) │ │
│ │ ├─ useAuth() → user, loading │ │
│ │ └─ Route 분기 (인증/비인증) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ HomePage │ │
│ │ ├─ useTodos() → todos[], loading │ │
│ │ ├─ ThemeContext → theme, toggleTheme │ │
│ │ ├─ i18next → t(), i18n │ │
│ │ └─ Components: AddTodoForm, TodoList │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Firebase Services (CRUD) │ │
│ │ ├─ addTodo(userId, text) │ │
│ │ ├─ getTodos(userId, callback) → unsubscribe │ │
│ │ ├─ toggleTodoCompleted(id, completed) │ │
│ │ └─ deleteTodo(id) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘// useAuth Hook
사용자 인증 상태를 실시간으로 추적
- onAuthStateChanged 리스너 등록
- user, loading 상태 반환
- cleanup 함수로 메모리 누수 방지
// LoginPage
- 이메일/비밀번호 입력
- 회원가입: createUserWithEmailAndPassword
- 로그인: signInWithEmailAndPassword
- 에러 핸들링: AbortError 필터링// Firestore 데이터 구조
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Timestamp;
userId: string;
}
// 보안 규칙
- allow list: 인증된 사용자만 쿼리 가능
- allow get: 자신의 문서만 읽기 가능
- allow create: 자신의 userId로만 생성
- allow update/delete: 자신의 문서만 수정/삭제// useTodos Hook
- getTodos() → onSnapshot 리스너 등록
- userId로 필터링된 쿼리
- createdAt 기준 내림차순 정렬
- 실시간 업데이트 자동 반영
- cleanup 시 unsubscribe() 호출// 지원 언어: en (영어), ko (한국어), ja (일본어)
// 브라우저 언어 자동 감지
// 사용자가 수동으로 언어 변경 가능
// 모든 UI 텍스트 번역 파일로 관리// ThemeContext
- localStorage에 테마 저장
- Bootstrap 5의 data-bs-theme 속성 활용
- light/dark 토글
- 앱 전체에 Context로 제공// 3단계 방어 시스템
1. fetch 오버라이드: AbortError를 소스에서 차단
2. unhandledrejection: 놓친 에러 캐치
3. console.error 필터링: inspector.js 로그 차단React + TypeScript + Vite로 Todo 앱을 만들고 싶어요. 다음 요구사항을 충족하는 프로젝트를 생성해주세요:
1. React 19 + TypeScript 5 + Vite 7 사용
2. Firebase Authentication + Firestore 사용
3. React Router DOM으로 라우팅
4. i18next로 다국어 지원 (영어, 한국어, 일본어)
5. Bootstrap 5 스타일링 (CDN 사용)
다음 명령어로 프로젝트를 생성하고 필요한 패키지를 설치해주세요:
npm create vite@latest todo-app -- --template react-ts
cd todo-app
npm install
그리고 다음 패키지들을 추가 설치해주세요:
npm install firebase react-router-dom i18next react-i18next i18next-browser-languagedetector
개발 도구도 설치해주세요:
npm install -D firebase-tools @types/node
package.json을 확인하고 모든 의존성이 올바르게 설치되었는지 확인해주세요.Firebase 프로젝트를 설정하고 싶어요. 다음 파일들을 생성해주세요:
1. src/firebase/config.ts - Firebase 초기화 파일
- Vite 환경 변수 (import.meta.env.VITE_*) 사용
- getAuth, getFirestore import
- 환경 변수 검증 로직 포함
2. src/firebase/services.ts - Firestore CRUD 서비스
- Todo 인터페이스 정의 (id, text, completed, createdAt, userId)
- addTodo(userId, text) - 새 Todo 추가
- getTodos(userId, callback) - 실시간 리스너 (where userId, orderBy createdAt desc)
- toggleTodoCompleted(docId, completed) - 완료 상태 토글
- deleteTodo(docId) - Todo 삭제
- 모든 함수에 AbortError 처리 포함 (error.name === 'AbortError'면 조용히 반환)
- permission-denied 에러는 명확한 메시지로 로깅
3. firestore.rules - Firestore 보안 규칙
- todos 컬렉션에 대해:
- allow list: 인증된 사용자만
- allow get: 자신의 문서만
- allow create: 자신의 userId로만
- allow update, delete: 자신의 문서만
4. firebase.json - Firebase 프로젝트 설정
- firestore rules 및 indexes 경로 지정
5. .env.local 템플릿 생성
- VITE_API_KEY, VITE_AUTH_DOMAIN, VITE_PROJECT_ID 등 환경 변수 예시
각 파일의 전체 코드를 TypeScript와 Firebase v10+ 최신 문법으로 작성해주세요.
React 17+ 에서는 JSX 변환 시 `import React`가 불필요하므로 포함하지 마세요.
type import는 `import type { ... }`나 `type ReactNode` 형식으로 작성해주세요.React Custom Hooks를 만들고 싶어요. 다음 두 개의 hook을 생성해주세요:
1. src/hooks/useAuth.ts
- Firebase Authentication 상태를 관리하는 hook
- onAuthStateChanged 리스너 등록
- user (User | null)와 loading (boolean) 상태 반환
- useEffect cleanup에서 unsubscribe 호출
- TypeScript 타입 안전성 보장
2. src/hooks/useTodos.ts
- useAuth hook 사용하여 현재 사용자 가져오기
- getTodos 서비스 사용하여 실시간 리스너 등록
- todos (Todo[])와 loading (boolean) 상태 반환
- user가 없으면 빈 배열 반환
- useEffect cleanup에서 리스너 해제
주의사항:
- React 17+이므로 `import React` 불필요
- type import는 `import type { User }` 형식 사용
- dependency array 정확히 설정
- 메모리 누수 방지를 위한 cleanup 함수 필수다크모드를 지원하는 ThemeContext를 만들고 싶어요.
src/context/ThemeContext.tsx 파일을 생성해주세요:
요구사항:
1. Theme 타입: 'light' | 'dark'
2. ThemeContextType 인터페이스: { theme: Theme, toggleTheme: () => void }
3. ThemeProvider 컴포넌트:
- localStorage에서 초기 테마 로드 (기본값: 'light')
- theme이 변경되면 document.documentElement.setAttribute('data-bs-theme', theme) 실행
- localStorage에 theme 저장
4. useTheme 커스텀 hook:
- ThemeContext 사용
- context가 undefined면 에러 발생
주의사항:
- type ReactNode는 `import { type ReactNode }` 형식으로 import
- React import 불필요
- children props 타입 명시다국어 지원 설정을 해주세요.
1. src/i18n.ts 파일 생성:
- i18next, react-i18next, i18next-browser-languagedetector import
- 영어(en), 한국어(ko), 일본어(ja) 번역 파일 import
- LanguageDetector 사용하여 브라우저 언어 자동 감지
- fallbackLng: 'en'
- interpolation.escapeValue: false
2. 번역 파일 생성:
- src/locales/en/translation.json
- src/locales/ko/translation.json
- src/locales/ja/translation.json
번역 키:
- loginTitle: "Login or Sign Up" / "로그인 또는 회원가입" / "ログインまたはサインアップ"
- emailPlaceholder: "Email" / "이메일" / "Eメール"
- passwordPlaceholder: "Password" / "비밀번호" / "パスワード"
- loginButton, signupButton, logoutButton
- todoTitle: "Todo List" / "할 일 목록" / "Todoリスト"
- addTaskButton, taskPlaceholder, deleteButton
- noTasks: "No tasks yet. Add one above!"
- loading: "Loading..." / "로딩 중..." / "読み込み中..."
- adding, deleting (로딩 상태 텍스트)
모든 파일을 JSON 형식으로 작성해주세요.Todo 추가 폼 컴포넌트를 만들고 싶어요.
src/components/AddTodoForm.tsx 파일을 생성해주세요:
요구사항:
1. Props: { userId: string }
2. 로컬 상태:
- text (string) - 입력 텍스트
- isAdding (boolean) - 추가 중 상태
3. handleSubmit 함수:
- e.preventDefault() 호출
- text.trim() 검증
- isAdding이면 early return
- setIsAdding(true) 설정
- try-catch-finally로 addTodo 호출
- 성공 시 setText('') 로 입력 필드 초기화
- permission-denied 에러는 명확히 로깅
- AbortError는 조용히 처리
- finally에서 setIsAdding(false)
4. UI:
- form onSubmit={handleSubmit}
- Bootstrap input-group 사용
- input: type="text", placeholder={t('taskPlaceholder')}
- button: type="submit", {isAdding ? t('adding') : t('addTaskButton')}
- isAdding 중에는 input과 button disabled
주의사항:
- useTranslation() hook 사용
- React import 불필요
- async/await 사용개별 Todo 아이템 컴포넌트를 만들고 싶어요.
src/components/TodoItem.tsx 파일을 생성해주세요:
요구사항:
1. Props: { todo: Todo }
2. 로컬 상태:
- isToggling (boolean) - 토글 중
- isDeleting (boolean) - 삭제 중
3. handleToggle 함수:
- isToggling이면 early return
- setIsToggling(true)
- try-catch-finally로 toggleTodoCompleted 호출
- permission-denied와 AbortError 처리
- finally에서 setIsToggling(false)
4. handleDelete 함수:
- isDeleting이면 early return
- setIsDeleting(true)
- try-catch-finally로 deleteTodo 호출
- permission-denied와 AbortError 처리
- finally에서 setIsDeleting(false)
5. UI:
- li.list-group-item
- checkbox: checked={todo.completed}, onChange={handleToggle}
- label: todo.completed이면 text-decoration-line-through, text-muted 클래스
- button: onClick={handleDelete}, {isDeleting ? t('deleting') : t('deleteButton')}
- isToggling이나 isDeleting 중에는 모든 컨트롤 disabled
주의사항:
- useTranslation() hook 사용
- async/await 사용
- 중복 클릭 방지 필수Todo 목록을 렌더링하는 간단한 컨테이너 컴포넌트를 만들어주세요.
src/components/TodoList.tsx 파일을 생성해주세요:
요구사항:
1. Props: { todos: Todo[] }
2. UI:
- ul.list-group
- todos.map()으로 TodoItem 렌더링
- key={todo.id}로 설정
주의사항:
- React import 불필요
- React.FC<TodoListProps> 타입 명시
- TodoItem import로그인/회원가입 페이지를 만들어주세요.
src/pages/LoginPage.tsx 파일을 생성해주세요:
요구사항:
1. 로컬 상태:
- email (string)
- password (string)
- error (string | null)
2. handleSignUp 함수:
- React.MouseEvent<HTMLButtonElement> 타입
- e.preventDefault() 호출
- email, password 검증
- try-catch로 createUserWithEmailAndPassword 호출
- AbortError는 조용히 처리
- 다른 에러는 setError(err.message)
3. handleLogIn 함수:
- handleSignUp과 유사하지만 signInWithEmailAndPassword 사용
4. UI:
- Bootstrap card 레이아웃
- h2: {t('loginTitle')}
- input email: placeholder={t('emailPlaceholder')}
- input password: placeholder={t('passwordPlaceholder')}
- button 로그인: onClick={handleLogIn}, type="submit"
- button 회원가입: onClick={handleSignUp}, type="button"
- error가 있으면 alert 표시
주의사항:
- useTranslation() hook 사용
- React 19의 event handler 타입 사용
- async/await 사용메인 페이지 (Todo 관리)를 만들어주세요.
src/pages/HomePage.tsx 파일을 생성해주세요:
요구사항:
1. Hooks:
- useAuth() → user
- useTodos() → todos, loading
- useTheme() → theme, toggleTheme
- useTranslation() → t, i18n
2. handleLogout 함수:
- signOut(auth).catch()
- AbortError 조용히 처리
3. handleLanguageChange 함수:
- i18n.changeLanguage(lng) 호출
4. UI 구조:
- card.card-header:
- h1: {t('todoTitle')}
- select: 언어 선택 (EN, KO, JA)
- button: 테마 토글 ({theme === 'dark' ? '🌙' : '☀️'})
- button: 로그아웃 {t('logoutButton')}
- card.card-body:
- AddTodoForm (user가 있을 때만)
- loading이면 spinner 표시
- todos.length === 0이고 loading이 아니면 {t('noTasks')}
- todos.length > 0이면 TodoList
주의사항:
- 모든 컴포넌트와 hook import
- Bootstrap 클래스 사용
- 조건부 렌더링 정확히 구현앱의 라우팅을 설정해주세요.
src/App.tsx 파일을 생성해주세요:
요구사항:
1. useAuth() hook으로 user, loading 가져오기
2. loading이면 "Loading..." 표시
3. BrowserRouter 사용:
- Route "/" - user가 있으면 HomePage, 없으면 /login으로 Navigate
- Route "/login" - user가 없으면 LoginPage, 있으면 /로 Navigate
4. 전체를 div.container.mt-5로 감싸기
주의사항:
- React Router DOM의 Navigate 컴포넌트 사용
- 인증 상태에 따른 리다이렉트 정확히 구현
- App.css import (빈 파일이어도 됨)앱의 진입점 파일을 만들어주세요. 특히 AbortError 차단 시스템을 포함해야 합니다.
src/main.tsx 파일을 생성해주세요:
요구사항:
1. 전역 AbortError 차단 시스템 (3단계):
1단계 - fetch 오버라이드:
- const originalFetch = window.fetch 저장
- window.fetch를 새 함수로 오버라이드
- .catch()에서 AbortError 또는 code === 'cancelled' 체크
- AbortError면 new Promise(() => {}) 반환 (완전히 삼킴)
- 다른 에러는 throw
2단계 - unhandledrejection 핸들러:
- window.addEventListener('unhandledrejection', ...)
- AbortError면 event.preventDefault()
- 다른 에러는 console.error로 로깅
3단계 - console.error 필터링:
- const originalConsoleError = console.error 저장
- console.error를 새 함수로 오버라이드
- 메시지에 'AbortError' 또는 'user aborted' 포함 체크
- AbortError 관련이면 return (로그 출력 안 함)
- 다른 에러는 originalConsoleError로 로깅
2. React 앱 렌더링:
- createRoot(document.getElementById('root')!)
- StrictMode로 감싸기
- ThemeProvider로 감싸기
- App 컴포넌트 렌더링
3. 필요한 import:
- React StrictMode, createRoot
- './index.css', './i18n' (부수효과 import)
- App, ThemeProvider
주의사항:
- 3단계 AbortError 차단 시스템이 가장 중요
- 주석으로 각 단계 명확히 표시
- originalFetch, originalConsoleError 변수 사용스타일링 파일들을 설정해주세요.
1. src/index.css:
- body의 background-color를 #f0f2f5로 설정
- !important 플래그 사용하여 Bootstrap 오버라이드
2. src/App.css:
- 빈 파일 또는 필요한 앱별 스타일 추가
3. index.html:
- Bootstrap 5 CSS CDN 추가 (<link> 태그)
- Bootstrap 5 JS CDN 추가 (<script> 태그, body 끝)
- 타이틀을 "Todo App"으로 변경
주의사항:
- Bootstrap 5.3+ 버전 사용
- dark mode 지원을 위한 data-bs-theme 속성 지원 버전TypeScript 설정을 검증하고 필요하면 수정해주세요.
tsconfig.app.json 파일을 확인하고:
1. verbatimModuleSyntax가 true로 설정되어 있는지 확인
2. 이 설정 때문에 type import는 반드시 `import type { ... }` 또는 inline `import { type ... }` 형식 사용
3. React 17+ JSX transform 사용 확인 (jsx: "react-jsx")
모든 .tsx 파일에서:
1. `import React`가 없는지 확인 (JSX 변환이므로 불필요)
2. type-only import는 올바른 형식인지 확인
3. 사용하지 않는 import가 없는지 확인 (TS6133 에러 방지)
npm run build를 실행하여 에러가 없는지 확인해주세요.Firebase 프로젝트를 설정하고 환경 변수를 구성해주세요.
1. Firebase Console에서 새 프로젝트 생성 (또는 기존 프로젝트 사용)
2. Authentication 활성화:
- Email/Password 로그인 활성화
3. Firestore Database 생성:
- 테스트 모드로 시작
4. Web 앱 추가하여 Firebase 설정 가져오기
5. .env.local 파일 생성:
VITE_API_KEY=your-api-key
VITE_AUTH_DOMAIN=your-project-id.firebaseapp.com
VITE_PROJECT_ID=your-project-id
VITE_STORAGE_BUCKET=your-project-id.firebasestorage.app
VITE_MESSAGING_SENDER_ID=your-sender-id
VITE_APP_ID=your-app-id
6. Firebase CLI로 보안 규칙 배포:
npx firebase login
npx firebase init firestore (기존 파일 덮어쓰지 않도록 주의)
npx firebase deploy --only firestore:rules
또는 Firebase Console에서 수동으로 firestore.rules 내용을 복사하여 배포
주의사항:
- .env.local은 .gitignore에 포함
- .env.example 파일을 만들어 변수 이름만 공유프로젝트를 빌드하고 테스트해주세요.
1. 빌드 테스트:
npm run build
- 에러가 없어야 함
- dist 폴더 생성 확인
2. 로컬 개발 서버 실행:
npm run dev
- http://localhost:5173 접속
- 브라우저 콘솔에 에러가 없어야 함
3. 기능 테스트 체크리스트:
- [ ] 회원가입 정상 작동
- [ ] 로그인 정상 작동
- [ ] Todo 추가 정상 작동
- [ ] Todo 체크/언체크 정상 작동
- [ ] Todo 삭제 정상 작동
- [ ] 다크모드 토글 정상 작동
- [ ] 언어 전환 정상 작동 (EN, KO, JA)
- [ ] 로그아웃 정상 작동
- [ ] 브라우저 콘솔에 AbortError 없음
- [ ] 브라우저 콘솔에 permission-denied 없음
4. 보안 규칙 테스트:
- [ ] 비로그인 사용자는 데이터 접근 불가
- [ ] 사용자 A는 사용자 B의 Todo 볼 수 없음
문제가 있으면 콘솔 에러를 확인하고 해당 단계로 돌아가 수정해주세요.TS6133: 'React' is declared but never used
→ import React 제거
TS1484: 'ReactNode' must be imported using type-only import
→ import { type ReactNode } 사용
TS2307: Cannot find module
→ npm install 재실행permission-denied
→ Firestore 보안 규칙 확인 및 배포
AbortError in console
→ main.tsx의 3단계 차단 시스템 확인
Missing environment variables
→ .env.local 파일 생성 및 변수 설정Cannot resolve module
→ package.json 의존성 확인
TypeScript compilation errors
→ tsconfig.json 설정 확인# 1. Git 저장소 생성
git init
git add .
git commit -m "Initial commit: Firebase Todo App"
# 2. GitHub에 푸시
git remote add origin <your-repo-url>
git push -u origin main
# 3. Netlify 설정
- Build command: npm run build
- Publish directory: dist
- Environment variables: VITE_* 변수들 설정# 1. Firebase Hosting 초기화
npx firebase init hosting
# 2. 설정
- Public directory: dist
- Single-page app: Yes
- GitHub Actions: No (선택)
# 3. 빌드 및 배포
npm run build
npx firebase deploy --only hosting1. 프로젝트 초기화 (Vite + React + TS)
↓
2. Firebase 설정 (config, services, rules)
↓
3. Custom Hooks (useAuth, useTodos)
↓
4. Context (ThemeContext)
↓
5. i18n 설정 (다국어)
↓
6. Components (AddTodoForm, TodoItem, TodoList)
↓
7. Pages (LoginPage, HomePage)
↓
8. App 라우팅
↓
9. main.tsx (AbortError 차단 시스템 포함)
↓
10. CSS 및 스타일링
↓
11. TypeScript 설정 검증
↓
12. Firebase 프로젝트 설정
↓
13. 빌드 및 테스트