Skip to content

JangAyeon/blockie

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

467 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Blockie

๊ฐœ์ธ ์žฌ๋ฌด ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ง€์ถœ ์ถ”์  ๋ฐ ๋ถ„์„ ๊ฐ€๊ณ„๋ถ€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

TypeScript Next.js NestJS React

ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

Blockie๋Š” ์‚ฌ์šฉ์ž์˜ ์ง€์ถœ์„ ์‹œ๊ฐ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋Š” ํ’€์Šคํƒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. ์ผ์ผ ์ง€์ถœ ๊ธฐ๋ก์„ ์ƒ‰์ƒ ํ๋ธŒ๋กœ ์‹œ๊ฐํ™”ํ•˜๊ณ , ์—ฐ์† ๊ธฐ๋ก ์ŠคํŠธ๋ฆญ(Streak) ์‹œ์Šคํ…œ์„ ํ†ตํ•ด ๊พธ์ค€ํ•œ ์žฌ๋ฌด ๊ด€๋ฆฌ๋ฅผ ๋…๋ คํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ

ํ”„๋กœ๋•ํŠธ ์™„์„ฑ๋„๋ณด๋‹ค๋Š” ๊พธ์ค€ํ•œ ๊ธฐ์ˆ ์  ๋„์ „๊ณผ ํ•™์Šต ๊ฒฝํ—˜์— ์ค‘์ ์„ ๋‘๊ณ , ํ‡ด๊ทผ ํ›„ ๊ณต๋ถ€ํ•œ ๊ฒƒ์„ ๋ง๋ถ™์—ฌ ๊ฐœ๋ฐœํ•˜๋ฉฐ ๊นŠ์ด ์žˆ๊ฒŒ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ผ์ผ ์ง€์ถœ ๊ธฐ๋ก์„ ์ƒ‰์ƒ ํ๋ธŒ๋กœ ์‹œ๊ฐํ™”ํ•˜๋Š” ์ปจ์…‰์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ  ์˜์—ญ(ํ”„๋ก ํŠธ์—”๋“œ, ๋ฐฑ์—”๋“œ, ๋ชจ๋…ธ๋ ˆํฌ, ์• ๋‹ˆ๋ฉ”์ด์…˜, ์ƒํƒœ ๊ด€๋ฆฌ ๋“ฑ)์„ ์ข…ํ•ฉ์ ์œผ๋กœ ๊ฒฝํ—˜ํ•˜๋ฉฐ ๊ฐœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ง€์ถœ ๊ด€๋ฆฌ: ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ง€์ถœ ์ถ”์  ๋ฐ ํ†ต๊ณ„ ๋ถ„์„
  • ์˜ˆ์‚ฐ ์„ค์ •: ์›”๋ณ„ ์˜ˆ์‚ฐ ์„ค์ • ๋ฐ ์‹ค์‹œ๊ฐ„ ์˜ˆ์‚ฐ ๋Œ€๋น„ ์ง€์ถœ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์‹œ๊ฐ์  ๋Œ€์‹œ๋ณด๋“œ: ์ง€์ถœ ๋‚ด์—ญ์„ ์ƒ‰์ƒ ํ๋ธŒ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๋…์ฐฝ์ ์ธ UI
  • ์ŠคํŠธ๋ฆญ ์‹œ์Šคํ…œ: ์—ฐ์† ๊ธฐ๋ก ์ผ์ˆ˜ ์ถ”์ ์œผ๋กœ ์Šต๊ด€ ํ˜•์„ฑ ์ง€์›
  • ๋‹ค๊ตญ์–ด ์ง€์›: ํ•œ๊ตญ์–ด, ์˜์–ด, ์ค‘๊ตญ์–ด ์ง€์›
  • ์‹ค์‹œ๊ฐ„ ๋ถ„์„: ์ผ๊ฐ„/์ฃผ๊ฐ„/์›”๊ฐ„ ์ง€์ถœ ํŒจํ„ด ๋ถ„์„ ๋ฐ ์ธ์‚ฌ์ดํŠธ ์ œ๊ณต

๊ฐœ๋ฐœ ์ผ์ง€

  1. ์‚ฌ์šฉ์ž ์ธ์ฆ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๋น ๋ฅด๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋ Œ๋”๋งํ• ๊นŒ?
  2. ์ง€์ถœ ๋ธ”๋ก ์‹œ๊ฐํ™”์—์„œ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ ์—ฌ์ •
  3. ๋‹ค๊ตญ์–ด ์„œ๋น„์Šค์—์„œ ์‹ค์šฉ์ ์ธ ์‚ฌ์šฉ์ž ์ค‘์‹ฌ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  4. ๊ทœ๋ชจ์— ๋งž๋Š” ์ •๋ง ํ˜„์‹ค์ ์ธ ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•๊ธฐ
  5. ๊ณ ๊ตฐ๋ถ„ํˆฌ ๋ชจ๋…ธ๋ ˆํฌ ๋ฐฐํฌ 1
  6. ๊ณ ๊ตฐ๋ถ„ํˆฌ ๋ชจ๋…ธ๋ ˆํฌ ๋ฐฐํฌ 2

์ปดํฌ๋„ŒํŠธ ๋ฐ ๋””์ž์ธ ์‹œ์Šคํ…œ

[์ง€์ถœ ๋“ฑ๋ก ํ™”๋ฉด]  [ํ๋ธŒ ๋Œ€์‹œ๋ณด๋“œ]  [์˜ˆ์‚ฐ ์„ค์ •]  [ํ†ต๊ณ„ ๋ถ„์„]

๊ธฐ์ˆ  ์Šคํƒ

Frontend

  • Framework: Next.js 15 (App Router) + React 19
  • Language: TypeScript 5.8
  • Styling: Tailwind CSS 4.0
  • State Management: Zustand 5.0
  • Data Fetching: TanStack React Query 5.8
  • Animation: Framer Motion 12.23
  • Charts: Chart.js, React-ChartJS-2, Lightweight Charts
  • i18n: next-intl

Backend

  • Framework: NestJS 11.0
  • Language: TypeScript 5.8
  • Database: PostgreSQL + Prisma ORM 6.9
  • Authentication: JWT + Passport.js
  • Validation: Class Validator
  • API Documentation: Swagger/OpenAPI

Monorepo & Infrastructure

  • Monorepo: Turborepo 2.5
  • Package Manager: npm 10.2.0
  • Testing: Jest 30.1 + React Testing Library
  • Build Tool: Turbopack (Next.js), tsup (UI library)
  • Code Quality: ESLint, TypeScript strict mode

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

color-expense/
โ”œโ”€โ”€ apps/
โ”‚   โ”œโ”€โ”€ web/              # Next.js ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
โ”‚   โ”œโ”€โ”€ api/              # NestJS ๋ฐฑ์—”๋“œ API ์„œ๋ฒ„
โ”‚   โ””โ”€โ”€ docs/             # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ
โ”œโ”€โ”€ packages/
โ”‚   โ”œโ”€โ”€ ui/               # ๊ณต์œ  React UI ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
โ”‚   โ”œโ”€โ”€ types/            # ๊ณต์œ  TypeScript ํƒ€์ž… ์ •์˜
โ”‚   โ”œโ”€โ”€ eslint-config/    # ESLint ์„ค์ •
โ”‚   โ””โ”€โ”€ typescript-config/# TypeScript ์„ค์ •
โ””โ”€โ”€ turbo.json            # Monorepo ๋นŒ๋“œ ์„ค์ •

์ฃผ์š” ๋””๋ ‰ํ† ๋ฆฌ ์„ค๋ช…

apps/web - ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

web/
โ”œโ”€โ”€ app/[locale]/         # Next.js App Router (๋‹ค๊ตญ์–ด ์ง€์›)
โ”‚   โ”œโ”€โ”€ (public)/         # ๊ณต๊ฐœ ๋ผ์šฐํŠธ (๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž…)
โ”‚   โ””โ”€โ”€ (private)/        # ์ธ์ฆ ํ•„์š” ๋ผ์šฐํŠธ
โ”‚       โ”œโ”€โ”€ expense/      # ์ง€์ถœ ๊ด€๋ฆฌ
โ”‚       โ”œโ”€โ”€ budget/       # ์˜ˆ์‚ฐ ์„ค์ •
โ”‚       โ”œโ”€โ”€ cube/         # ํ๋ธŒ ๋Œ€์‹œ๋ณด๋“œ
โ”‚       โ”œโ”€โ”€ mypage/       # ๋งˆ์ดํŽ˜์ด์ง€
โ”‚       โ””โ”€โ”€ onboarding/   # ์˜จ๋ณด๋”ฉ
โ”œโ”€โ”€ @component/
โ”‚   โ””โ”€โ”€ features/         # ๊ธฐ๋Šฅ๋ณ„ ์ปดํฌ๋„ŒํŠธ
โ”œโ”€โ”€ @hook/
โ”‚   โ”œโ”€โ”€ api/              # API ์—ฐ๋™ ํ›…
โ”‚   โ””โ”€โ”€ business/         # ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ํ›…
โ”œโ”€โ”€ @utils/               # ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
โ”œโ”€โ”€ @store/               # Zustand ์ƒํƒœ ๊ด€๋ฆฌ
โ””โ”€โ”€ messages/             # i18n ๋ฒˆ์—ญ ํŒŒ์ผ

apps/api - ๋ฐฑ์—”๋“œ API

api/src/
โ”œโ”€โ”€ auth/                 # ์ธ์ฆ ๋ชจ๋“ˆ (JWT)
โ”œโ”€โ”€ users/                # ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ
โ”œโ”€โ”€ expenses/             # ์ง€์ถœ CRUD ๋ฐ ํ†ต๊ณ„
โ”œโ”€โ”€ budget/               # ์˜ˆ์‚ฐ ๊ด€๋ฆฌ
โ””โ”€โ”€ prisma/               # Prisma ์Šคํ‚ค๋งˆ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

packages/ui - UI ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

ui/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ button/           # ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ card/             # ์นด๋“œ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ””โ”€โ”€ animation/        # ์• ๋‹ˆ๋ฉ”์ด์…˜ ์œ ํ‹ธ
โ””โ”€โ”€ storybook/            # Storybook ์„ค์ •

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ

User (์‚ฌ์šฉ์ž)

model User {
  id        String   @id @default(cuid())
  name      String
  email     String   @unique
  phone     String
  expenses  Expense[]
  budgets   Budget[]
  createdAt DateTime @default(now())
}

Expense (์ง€์ถœ)

model Expense {
  id          String   @id @default(cuid())
  userId      String
  amount      Int
  category    String
  expenseDate DateTime  // ์‹ค์ œ ์ง€์ถœ ๋‚ ์งœ
  createdAt   DateTime @default(now())
  user        User     @relation(fields: [userId], references: [id])
}

Budget (์˜ˆ์‚ฐ)

model Budget {
  id        String   @id @default(cuid())
  userId    String
  year      Int
  month     Int
  amount    Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  user      User     @relation(fields: [userId], references: [id])

  @@unique([userId, year, month])
}

API ์—”๋“œํฌ์ธํŠธ

์ธ์ฆ (Authentication)

  • POST /auth/signin - ๋กœ๊ทธ์ธ
  • POST /auth/signup - ํšŒ์›๊ฐ€์ž…
  • POST /auth/refresh - ํ† ํฐ ๊ฐฑ์‹ 

์ง€์ถœ ๊ด€๋ฆฌ (Expenses)

  • POST /expenses - ์ง€์ถœ ์ƒ์„ฑ
  • GET /expenses - ์ง€์ถœ ๋ชฉ๋ก ์กฐํšŒ
  • PATCH /expenses/:id - ์ง€์ถœ ์ˆ˜์ •
  • DELETE /expenses/:id - ์ง€์ถœ ์‚ญ์ œ
  • GET /expenses/stats/daily - ์ผ์ผ ํ†ต๊ณ„
  • GET /expenses/stats/weekly - ์ฃผ๊ฐ„ ํ†ต๊ณ„
  • GET /expenses/stats/monthly - ์›”๊ฐ„ ํ†ต๊ณ„
  • GET /expenses/stats/category - ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ถ„์„
  • GET /expenses/stats/streak - ์—ฐ์† ๊ธฐ๋ก ํ†ต๊ณ„
  • GET /expenses/analysis - ํŠธ๋ Œ๋“œ ๋ถ„์„

์˜ˆ์‚ฐ ๊ด€๋ฆฌ (Budget)

  • POST /budget - ์˜ˆ์‚ฐ ์ƒ์„ฑ
  • GET /budget - ์˜ˆ์‚ฐ ์กฐํšŒ
  • PATCH /budget/:id - ์˜ˆ์‚ฐ ์ˆ˜์ •
  • GET /budget/recommendation - ์˜ˆ์‚ฐ ์ถ”์ฒœ

์‹œ์ž‘ํ•˜๊ธฐ

์š”๊ตฌ์‚ฌํ•ญ

  • Node.js >= 18
  • npm 10.2.0
  • PostgreSQL

์„ค์น˜

# ์ €์žฅ์†Œ ํด๋ก 
git clone https://github.com/JangAyeon/color-expense.git
cd color-expense

# ์˜์กด์„ฑ ์„ค์น˜
npm install

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •

apps/api/.env

DATABASE_URL="postgresql://user:password@localhost:5432/color_expense"
JWT_SECRET="your-jwt-secret"
JWT_EXPIRES_IN="7d"

apps/web/.env.local

NEXT_PUBLIC_API_URL="http://localhost:3001"
NEXT_PUBLIC_SUPABASE_URL="your-supabase-url"
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-supabase-key"

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

cd apps/api
npx prisma migrate dev
npx prisma generate

์‹คํ–‰

# ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ (monorepo)
npm run dev

# ๊ฐœ๋ณ„ ์‹คํ–‰
npm run dev:web    # ํ”„๋ก ํŠธ์—”๋“œ๋งŒ ์‹คํ–‰ (http://localhost:3000)
npm run dev:api    # ๋ฐฑ์—”๋“œ๋งŒ ์‹คํ–‰ (http://localhost:3001)

๋นŒ๋“œ

# ์ „์ฒด ๋นŒ๋“œ
npm run build

# ํƒ€์ž… ์ฒดํฌ
npm run check-types

# ๋ฆฐํŠธ
npm run lint

# ํ…Œ์ŠคํŠธ
npm run test

์ฃผ์š” ๊ตฌํ˜„ ์‚ฌํ•ญ

1. ๋ชจ๋…ธ๋ ˆํฌ ์•„ํ‚คํ…์ฒ˜

  • Turborepo๋ฅผ ํ™œ์šฉํ•œ ํšจ์œจ์ ์ธ ๋นŒ๋“œ ์บ์‹ฑ ๋ฐ ๋ณ‘๋ ฌ ์‹คํ–‰
  • ๊ณต์œ  UI ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํƒ€์ž… ์ •์˜๋ฅผ ํ†ตํ•œ ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ
  • ์ผ๊ด€๋œ ESLint ๋ฐ TypeScript ์„ค์ • ๊ณต์œ 

2. ํ๋ธŒ ์‹œ๊ฐํ™”

apps/web/@component/features/cube

  • Framer Motion์„ ํ™œ์šฉํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ๋ธŒ ๊ทธ๋ฆฌ๋“œ
  • ์ง€์ถœ ๊ธˆ์•ก์— ๋”ฐ๋ฅธ ๋™์  ์ƒ‰์ƒ ํ‘œํ˜„
  • ์—ฐ์† ๊ธฐ๋ก ์ŠคํŠธ๋ฆญ ์‹œ๊ฐํ™”

3. ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”

  • React Query๋ฅผ ํ™œ์šฉํ•œ ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ
  • Optimistic Updates๋กœ ๋น ๋ฅธ UX ์ œ๊ณต
  • ์ž๋™ ๋ฆฌํŒจ์นญ ๋ฐ ์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต

4. ๋‹ค๊ตญ์–ด ์ง€์› (i18n)

apps/web/messages

  • next-intl์„ ํ™œ์šฉํ•œ ์ •์  ๋ฉ”์‹œ์ง€ ๋ฒˆ์—ญ
  • URL ๊ธฐ๋ฐ˜ ๋กœ์ผ€์ผ ๋ผ์šฐํŒ… (/ko, /en, /zh)
  • ๋™์  ๋กœ์ผ€์ผ ์ „ํ™˜ UI

5. ๋ฐ˜์‘ํ˜• ๋””์ž์ธ

  • Tailwind CSS๋ฅผ ํ™œ์šฉํ•œ ๋ชจ๋ฐ”์ผ ์šฐ์„  ๋””์ž์ธ
  • ํƒœ๋ธ”๋ฆฟ ๋ฐ ๋ฐ์Šคํฌํ†ฑ ํ™˜๊ฒฝ ์ตœ์ ํ™”
  • ํ„ฐ์น˜ ์ œ์Šค์ฒ˜ ์ง€์›

6. ํƒ€์ž… ์•ˆ์ •์„ฑ

  • ์—„๊ฒฉํ•œ TypeScript ์„ค์ • (strict: true)
  • Prisma๋ฅผ ํ†ตํ•œ ํƒ€์ž… ์•ˆ์ „ํ•œ DB ์ฟผ๋ฆฌ
  • ํ”„๋ก ํŠธ์—”๋“œ-๋ฐฑ์—”๋“œ ๊ฐ„ ํƒ€์ž… ๊ณต์œ  (@repo/types)

7. ํ…Œ์ŠคํŠธ

  • Jest ๊ธฐ๋ฐ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ
  • React Testing Library๋ฅผ ํ™œ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ
  • ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ

์„ฑ๋Šฅ ์ตœ์ ํ™”

  • ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…: Next.js ๋™์  import ํ™œ์šฉ
  • ์ด๋ฏธ์ง€ ์ตœ์ ํ™”: Next.js Image ์ปดํฌ๋„ŒํŠธ
  • ๋นŒ๋“œ ์ตœ์ ํ™”: Turbopack์„ ํ†ตํ•œ ๋น ๋ฅธ ๊ฐœ๋ฐœ ์„œ๋ฒ„
  • ์บ์‹ฑ ์ „๋žต: React Query์˜ staleTime ๋ฐ cacheTime ์„ค์ •
  • ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ ์ตœ์ ํ™”: Tree shaking ๋ฐ ๋™์  import

ํ–ฅํ›„ ๊ณ„ํš

  • PWA ์ง€์› (์˜คํ”„๋ผ์ธ ๋ชจ๋“œ)
  • ์ง€์ถœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ (ํ‘ธ์‹œ ์•Œ๋ฆผ)
  • AI ๊ธฐ๋ฐ˜ ์ง€์ถœ ์˜ˆ์ธก ๋ฐ ์ถ”์ฒœ
  • ์˜์ˆ˜์ฆ OCR ์Šค์บ” ๊ธฐ๋Šฅ
  • ์†Œ์…œ ๊ณต์œ  ๊ธฐ๋Šฅ (์ง€์ถœ ํ†ต๊ณ„ ๊ณต์œ )
  • ๋‹คํฌ ๋ชจ๋“œ ์ง€์›
  • E2E ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ (Playwright)

๋ฐฐ์šด ์  ๋ฐ ๊ฐœ์„  ์‚ฌํ•ญ

๊ธฐ์ˆ ์  ์„ฑ์žฅ

  1. ๋ชจ๋…ธ๋ ˆํฌ ๊ด€๋ฆฌ: Turborepo๋ฅผ ํ†ตํ•œ ํšจ์œจ์ ์ธ ๋ฉ€ํ‹ฐ ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ ๊ฒฝํ—˜
  2. ํ’€์Šคํƒ ๊ฐœ๋ฐœ: Next.js์™€ NestJS๋ฅผ ์—ฐ๋™ํ•œ End-to-End ๊ฐœ๋ฐœ ๊ฒฝํ—˜
  3. ํƒ€์ž… ์•ˆ์ •์„ฑ: TypeScript๋ฅผ ํ™œ์šฉํ•œ ํƒ€์ž… ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค ํ™•๋ฆฝ
  4. ์ƒํƒœ ๊ด€๋ฆฌ: React Query์™€ Zustand๋ฅผ ์กฐํ•ฉํ•œ ํšจ์œจ์ ์ธ ์ƒํƒœ ๊ด€๋ฆฌ ์ „๋žต
  5. ๊ตญ์ œํ™”: next-intl์„ ํ™œ์šฉํ•œ ๋‹ค๊ตญ์–ด ์ง€์› ๊ตฌํ˜„