βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (React 19 + TanStack Router) β
β ββββββββββββ ββββββββββββ ββββββββββββββ ββββββββββββ β
β β MapView β β Sidebar β βDetailPanel β β Filters β β
β β(NaverMap)β β(λͺ©λ‘+κ²μ)β β(리뷰/λΈλ‘κ·Έ)β β(λμ΄λλ±)β β
β ββββββ¬ββββββ ββββββ¬ββββββ βββββββ¬βββββββ ββββββ¬ββββββ β
β βββββββββββββββ΄ββββββββββββββ΄ββββββββββββββ β
β β createServerFn (RPC) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Server Layer (TanStack Start SSR) β
β parking.ts β reviews.ts β votes.ts β admin.ts β
β transforms.ts (μμ λ³ν) β auth (better-auth) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Database (Cloudflare D1 + Drizzle ORM) β
β 17 tables β parking_lot_stats (μ¬μ κ³μ° μ μ) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Data Pipeline (scripts/) β
β CSV κ°μ Έμ€κΈ° β ν¬λ‘€λ§ β κ°μ±λΆμ β λ² μ΄μ§μ μ μ μ°μΆ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| λ μ΄μ΄ | κΈ°μ | λΉκ³ |
|---|---|---|
| νλ μμν¬ | TanStack Start (SSR) + React 19 | νμΌ κΈ°λ° λΌμ°ν , isomorphic RPC |
| μ€νμΌ | Tailwind CSS v4 + shadcn/ui | new-york μ€νμΌ, zinc, oklch μ»¬λ¬ |
| μ§λ | Naver Maps (react-naver-maps) | μλ²μ¬μ΄λ ν΄λ¬μ€ν°λ§ |
| DB | Cloudflare D1 + Drizzle ORM | νμ΄λΈλ¦¬λ SQL (Raw + ORM) |
| μΈμ¦ | better-auth | μΉ΄μΉ΄μ€/λ€μ΄λ²/κ΅¬κΈ + μ΅λͺ |
| λ°°ν¬ | Cloudflare Workers | @cloudflare/vite-plugin SSR |
| ν μ€νΈ | Vitest + Testing Library | jsdom νκ²½ |
| λ°νμ | bun (λ‘컬) / workerd (νλ‘λμ ) |
index.tsx (λ©μΈ λ μ΄μμ)
βββ Header βββ SearchBar + UserMenu/LoginModal
βββ ParkingSidebar βββ ParkingCard[] (λͺ©λ‘)
βββ MapView βββ λ§μ»€ (κ°λ³/ν΄λ¬μ€ν°), λ΄ μμΉ
βββ FloatingFilters βββ νν° ν κΈ (무λ£/곡μ/λμ΄λ)
βββ ParkingDetailPanel
βββ VoteBookmarkBar (ππβ)
βββ ParkingTabs (리뷰 / λΈλ‘κ·Έ / λ―Έλμ΄)
μ§λ λ§μ»€ UX
createServerFn() (TanStack isomorphic RPC) ν¨ν΄μΌλ‘ ν΄λΌμ΄μΈνΈβμλ² ν΅μ .
| ν¨μ | μν | 쿼리 λ°©μ |
|---|---|---|
fetchParkingLots() |
λ°μ΄λ+νν° κΈ°λ° μ£Όμ°¨μ₯ λͺ©λ‘ | Raw SQL (κ³΅κ° μΏΌλ¦¬ + λμ WHERE) |
fetchParkingClusters() |
그리λ ν΄λ¬μ€ν°λ§ | Raw SQL |
searchParkingLots() |
μ΄λ¦/μ£Όμ/POI κ²μ | Raw SQL LIKE |
createReview() |
리뷰 μμ± (24h μ ν) | Drizzle ORM |
toggleVote() |
μΆμ²/λΉμΆμ² | Drizzle ORM |
fetchSiteStats() |
ν΅κ³ (6μκ° μΊμ) | Drizzle ORM |
νμ΄λΈλ¦¬λ SQL μ λ΅: 볡μ‘ν 쿼리(ν΄λ¬μ€ν°λ§, bounds, JOIN)λ sql.raw(), λ¨μ CRUDλ Drizzle ORM νμ
μΈμ΄ν.