--- id: wiki-2026-0508-철벽-수비대-인터페이스-설계-전략 title: 철벽 수비대 인터페이스 설계 전략 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Defensive UI, Resilient Frontend Design, Hardened Interface] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [frontend, ui-design, resilience, defensive-programming] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react --- # 철벽 수비대 인터페이스 설계 전략 ## 매 한 줄 > **"매 UI 는 매 적대적 입력 / 네트워크 실패 / 동시 변경 의 환경에서 매 buckle 없이 동작해야 한다"**. 매 철벽 수비대 (Hardened UI) 는 invalid props, race conditions, 부분 실패, hostile users 를 매 graceful 한 fallback 으로 처리하는 설계 철학. 매 2026 modern stack — React 19 Server Components + Suspense + Error Boundaries — 가 매 native 한 defensive primitives 를 제공. ## 매 핵심 ### 매 위협 모델 - **Bad data**: API 가 매 schema 위반 (null, missing field, wrong type). - **Bad timing**: race conditions — 매 stale closure, 매 out-of-order responses. - **Bad network**: timeouts, partial failures, retries. - **Bad users**: XSS payloads, paste 공격, 매 keyboard shortcut 남용. - **Bad state**: 매 corrupted localStorage, 매 stale cache. ### 매 방어 layer - **Schema layer**: Zod / Valibot 로 매 runtime validation. - **Boundary layer**: Error Boundary + Suspense. - **Retry layer**: TanStack Query / SWR 가 매 exponential backoff. - **Sanitization layer**: DOMPurify, CSP nonce. - **Fallback layer**: Skeleton, empty state, error state. ### 매 응용 1. Banking / fintech — 매 transactional UI 에서 매 double-submit 방지. 2. Healthcare records — 매 stale data 보다 매 명시적 error 우선. 3. Live collaboration — 매 conflict resolution UI. ## 💻 패턴 ### Zod schema-first parsing ```typescript import { z } from "zod"; const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), age: z.number().int().min(0).max(150), }); async function fetchUser(id: string) { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); // 매 parse — 매 invalid data 면 throw, downstream 은 매 typed safe return UserSchema.parse(await res.json()); } ``` ### Error Boundary + retry ```tsx import { ErrorBoundary } from "react-error-boundary"; function UserView() { return ( (

문제 발생: {error.message}

)} onReset={() => queryClient.invalidateQueries(["user"])} > }>
); } ``` ### Stale closure 방지 (useEffectEvent) ```tsx import { useEffectEvent } from "react"; function ChatRoom({ roomId, onMessage }) { const handleMessage = useEffectEvent((msg) => { onMessage(msg, roomId); }); useEffect(() => { const conn = connect(roomId); conn.on("message", handleMessage); return () => conn.disconnect(); }, [roomId]); // 매 onMessage 가 매 deps 안 — 매 stale 없음 } ``` ### Race-condition-safe fetching ```typescript useEffect(() => { let cancelled = false; fetch(`/api/search?q=${query}`) .then(r => r.json()) .then(data => { if (!cancelled) setResults(data); }); return () => { cancelled = true; }; }, [query]); ``` ### XSS-safe rich text ```tsx import DOMPurify from "isomorphic-dompurify"; function RichText({ html }: { html: string }) { const clean = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }); return
; } ``` ### Optimistic update + rollback ```tsx const mutation = useMutation({ mutationFn: updateTodo, onMutate: async (newTodo) => { await queryClient.cancelQueries(["todos"]); const prev = queryClient.getQueryData(["todos"]); queryClient.setQueryData(["todos"], (old) => old.map(t => t.id === newTodo.id ? newTodo : t) ); return { prev }; }, onError: (_err, _new, ctx) => { queryClient.setQueryData(["todos"], ctx.prev); // 매 rollback }, onSettled: () => queryClient.invalidateQueries(["todos"]), }); ``` ### Idempotent submit (double-click 방지) ```tsx const [pending, startTransition] = useTransition(); const submit = (e) => { e.preventDefault(); if (pending) return; startTransition(() => mutation.mutate(formData)); }; ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | External API 응답 | Zod validation 필수 | | Component tree crash 위험 | ErrorBoundary 감싸기 | | Async race | AbortController + cleanup | | User-generated HTML | DOMPurify sanitize | | Critical mutation (payment) | Idempotency key + disable button | **기본값**: Zod + ErrorBoundary + TanStack Query + DOMPurify 의 layered defense. ## 🔗 Graph - 부모: [[Large_Frontend_Projects|Frontend Architecture]] - 변형: [[Error Boundary]] · [[Optimistic UI]] - Adjacent: [[CSP]] ## 🤖 LLM 활용 **언제**: 매 production-grade UI, 매 finance / healthcare, 매 untrusted user input 처리. **언제 X**: 매 internal prototype, 매 trusted data only. ## ❌ 안티패턴 - **Trust API blindly**: 매 schema 검증 없이 destructuring → runtime crash. - **Catch and ignore**: `try { ... } catch {}` — 매 silent failure. - **dangerouslySetInnerHTML 직접**: 매 sanitize 없이 → XSS. - **Stale closure**: 매 useEffect deps 누락 → 매 outdated state. ## 🧪 검증 / 중복 - Verified (React 19 docs, OWASP, Kent C. Dodds: "Use react-error-boundary"). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — defensive UI 7 patterns |