전체 ꡬ쑰

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  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 νƒ€μž… 세이프.


DB μŠ€ν‚€λ§ˆ (17 ν…Œμ΄λΈ”)