stats.takjakim.kr 개발기 (2): 설계
Method 개발기 시리즈 (2/4)
개요

이 파트에서는 Method 프로젝트의 설계를 다룹니다:
- 기술 스택 선정
- 시스템 아키텍처
- 데이터 모델
- 콘텐츠 구조
1. 기술 스택 선정
1.1 프론트엔드
| 기술 |
버전 |
선택 이유 |
| Next.js |
15 |
App Router, React Server Components |
| React |
19 |
최신 기능 (Suspense, Streaming) |
| TypeScript |
5.0 |
타입 안정성, DX |
| Tailwind CSS |
3.4 |
빠른 스타일링, 반응형 |
| shadcn/ui |
- |
접근성, 커스터마이징 |
1.2 콘텐츠
| 기술 |
선택 이유 |
| MDX |
마크다운 + React 컴포넌트 |
| next-mdx-remote v6 |
RSC 지원, 동적 로딩 |
1.3 Python 실행
| 기술 |
선택 이유 |
| Pyodide |
서버 없이 브라우저 내 실행 |
| WebAssembly |
네이티브급 성능 |
1.4 백엔드
| 기술 |
선택 이유 |
| Supabase |
PostgreSQL + Auth + 무료 티어 |
| Prisma |
Type-safe ORM |
| NextAuth.js v5 |
Google OAuth + Credentials |
1.5 인프라
| 기술 |
선택 이유 |
| Vercel |
Next.js 최적화, 자동 CI/CD |
| GitHub |
코드 저장소, PR Preview |
2. 시스템 아키텍처
2.1 전체 아키텍처
┌──────────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────┐ │
│ │ React │ │ MDX │ │ Pyodide (WASM) │ │
│ │ Components │ │ Lessons │ │ numpy, pandas, scipy │ │
│ │ (shadcn/ui) │ │ (RSC render) │ │ scikit-learn │ │
│ └────────────────┘ └────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ HTTPS │
└───────────┬───────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ NEXT.JS 15 SERVER (Vercel Edge) │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────┐ │
│ │ App Router │ │ API Routes │ │ next-mdx-remote │ │
│ │ (File-based) │ │ /api/auth │ │ (RSC compile) │ │
│ │ │ │ /api/progress │ │ │ │
│ └────────────────┘ └────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ Prisma ORM │
└───────────┬───────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ SUPABASE │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────┐ │
│ │ PostgreSQL │ │ Auth │ │ Storage │ │
│ │ Users, Progress │ │ (OAuth) │ │ (Future: Files) │ │
│ └────────────────┘ └────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
2.2 데이터 흐름
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ User │────▶│ Next.js │────▶│ Prisma │────▶│Supabase │
│ Browser │◀────│ Server │◀────│ ORM │◀────│ DB │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│
│ Pyodide (Client-side only)
▼
┌─────────┐
│ Python │ ← 서버 통신 없음
│ WASM │
└─────────┘
2.3 인증 흐름
┌─────────┐ ┌─────────────┐ ┌──────────┐
│ User │────▶│ NextAuth │────▶│ Google │
│ │◀────│ v5 │◀────│ OAuth │
└─────────┘ └──────┬──────┘ └──────────┘
│
▼
┌─────────────┐
│ Prisma │
│ Adapter │
└──────┬──────┘
│
▼
┌─────────────┐
│ Supabase │
│ PostgreSQL │
└─────────────┘
3. 데이터 모델
3.1 ERD
┌─────────────────────┐ ┌─────────────────────┐
│ User │ │ Progress │
├─────────────────────┤ ├─────────────────────┤
│ id (PK) │──┐ │ id (PK) │
│ email (UNIQUE) │ │ │ lessonId │
│ name │ │ │ completed │
│ password? │ └───▶│ userId (FK) │
│ emailVerified? │ │ createdAt │
│ image? │ │ updatedAt │
│ createdAt │ └─────────────────────┘
│ updatedAt │
└─────────────────────┘
│
│ NextAuth.js Relations
▼
┌─────────────────────┐ ┌─────────────────────┐
│ Account │ │ Session │
├─────────────────────┤ ├─────────────────────┤
│ provider │ │ sessionToken │
│ providerAccountId │ │ expires │
│ type │ │ userId (FK) │
│ access_token │ └─────────────────────┘
│ userId (FK) │
└─────────────────────┘
3.2 Prisma 스키마
// prisma/schema.prisma
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
password String? // 이메일/비밀번호 가입 시
accounts Account[]
sessions Session[]
progress Progress[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Progress {
id String @id @default(cuid())
lessonId String // "descriptive-statistics/mean"
completed Boolean @default(false)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([lessonId, userId])
@@index([userId])
}
// NextAuth.js 관련 모델들...
4. 콘텐츠 구조
4.1 디렉토리 구조
content/
├── lessons/
│ ├── ko/ # 한국어 레슨
│ │ ├── descriptive-statistics/ # 기술통계
│ │ │ ├── mean.mdx # 평균
│ │ │ ├── median.mdx # 중앙값
│ │ │ ├── variance.mdx # 분산
│ │ │ └── correlation.mdx # 상관관계
│ │ │
│ │ ├── probability-distributions/ # 확률분포
│ │ │ ├── probability-basics.mdx
│ │ │ ├── binomial-distribution.mdx
│ │ │ └── normal-distribution.mdx
│ │ │
│ │ ├── inferential-statistics/ # 추론통계
│ │ │ ├── sampling.mdx
│ │ │ ├── central-limit-theorem.mdx
│ │ │ ├── confidence-intervals.mdx
│ │ │ └── hypothesis-testing.mdx
│ │ │
│ │ ├── regression-analysis/ # 회귀분석
│ │ │ ├── simple-regression.mdx
│ │ │ └── multiple-regression.mdx
│ │ │
│ │ ├── factor-analysis/ # 요인분석
│ │ │ ├── exploratory-factor-analysis.mdx
│ │ │ └── confirmatory-factor-analysis.mdx
│ │ │
│ │ ├── advanced-regression/ # 고급 회귀
│ │ │ ├── hierarchical-regression.mdx
│ │ │ ├── logistic-regression.mdx
│ │ │ ├── moderated-mediation.mdx
│ │ │ └── mediated-moderation.mdx
│ │ │
│ │ └── sem/ # 구조방정식
│ │ └── structural-equation-modeling.mdx
│ │
│ └── en/ # 영어 레슨
│ └── ... (미러 구조)
│
└── exercises/
├── descriptive-statistics/
│ ├── mean.json # 평균 연습문제
│ ├── median.json
│ └── ...
└── ...
4.2 레슨 파일 구조 (MDX)
---
title: "평균 (Mean)"
description: "데이터의 중심을 나타내는 가장 기본적인 측도"
difficulty: "beginner"
duration: 15
prerequisites: []
---
# 평균이란?
평균은 <BlurWord>모든 관측값의 합</BlurWord>을 개수로 나눈 값입니다.
## 수식
$$
\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i
$$
## 실습
<InteractiveCode
title="평균 계산하기"
expectedValue="27.625"
starterCode="import numpy as np data = [15, 18, 21, 19, 17, 20, 16, 95] result = np.mean(data)"
/>
## 퀴즈
<QuestionCard>
이상치가 있을 때 평균의 특성은?
</QuestionCard>
<RevealAnswer>
이상치에 민감하여 왜곡될 수 있음
</RevealAnswer>
4.3 연습문제 파일 구조 (JSON)
{
"title": "평균 연습문제",
"description": "평균 계산 실습",
"exercises": [
{
"id": "mean-001",
"type": "code",
"difficulty": "easy",
"question": "주어진 데이터의 산술평균을 계산하세요.",
"starterCode": "import numpy as np\ndata = [10, 20, 30, 40, 50]\nresult = ___",
"expectedValue": 30,
"tolerance": 0.01,
"hints": ["np.mean() 함수를 사용하세요"]
},
{
"id": "mean-002",
"type": "code",
"difficulty": "medium",
"question": "가중평균을 계산하세요.",
"starterCode": "weights = [0.3, 0.3, 0.4]\nvalues = [80, 90, 85]\nresult = ___",
"expectedValue": 85,
"tolerance": 0.01
}
]
}
5. 라우팅 설계
5.1 App Router 구조
app/
├── [locale]/ # i18n (ko, en)
│ ├── page.tsx # 홈페이지 /
│ ├── layout.tsx # 공통 레이아웃
│ │
│ ├── learn/
│ │ ├── page.tsx # 학습 목록 /learn
│ │ └── [topic]/
│ │ └── [lesson]/
│ │ ├── page.tsx # 레슨 /learn/descriptive-statistics/mean
│ │ └── exercises/
│ │ └── page.tsx # 연습문제 /learn/.../mean/exercises
│ │
│ ├── dashboard/
│ │ └── page.tsx # 대시보드 /dashboard
│ │
│ └── auth/
│ ├── signin/
│ │ └── page.tsx # 로그인 /auth/signin
│ ├── signup/
│ │ └── page.tsx # 회원가입 /auth/signup
│ └── error/
│ └── page.tsx # 에러 /auth/error
│
└── api/
├── auth/
│ └── [...nextauth]/
│ └── route.ts # NextAuth API
└── progress/
└── route.ts # 진도 저장 API
5.2 URL 설계
| URL |
페이지 |
인증 필요 |
/ko |
홈페이지 |
X |
/ko/learn |
학습 목록 |
X |
/ko/learn/[topic]/[lesson] |
레슨 |
X |
/ko/learn/.../exercises |
연습문제 |
X |
/ko/dashboard |
대시보드 |
O |
/ko/auth/signin |
로그인 |
X |
/ko/auth/signup |
회원가입 |
X |
설계 원칙
Server-First
- React Server Components 우선 사용
- 클라이언트 컴포넌트는 인터랙션 필요한 곳만
Type-Safety
- 모든 곳에 TypeScript
- Prisma로 DB 타입 자동 생성
Progressive Enhancement
- 비로그인도 학습 가능
- 로그인 시 진도 저장 추가