--- id: wiki-2026-0508-hydration-mismatch-and-ssr-debug title: Hydration Mismatch and SSR Debugging category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Hydration Mismatch, SSR Debug, Hydration Error] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [react, ssr, hydration, debugging] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react-next --- # Hydration Mismatch and SSR Debugging ## 매 한 줄 > **"매 server-rendered HTML 과 매 client first render 가 매 다르면 매 React 가 매 throw — 매 일관된 input 이 매 핵심."**. 매 typical 원인: Date.now / Math.random / window 접근 / locale / 매 third-party DOM 조작. React 19 부터 매 error message 가 매 어떤 attribute 가 매 mismatch 인지 매 정확히 표시. ## 매 핵심 ### 매 원인 분류 - **Time / random**: server 의 매 시각 ≠ client 의 매 시각. - **Locale / timezone**: Intl.DateTimeFormat 의 매 다른 결과. - **Window / document**: server 에 매 없음. - **User-agent branching**: useragent 의 매 다른 처리. - **Third-party script**: AdSense, Cookiebot 의 매 DOM 변경. - **Browser extension**: Grammarly, Dark Reader 의 매 inject. - **Conditional rendering on `typeof window`**: 매 안티패턴. ### 매 React 의 매 행동 (19) - 매 mismatch detection — 매 server HTML 을 매 polluted attribute 로 매 표시. - 매 partial recovery — 매 mismatch boundary 만 매 client re-render. - 매 보존되는 attribute (data-, aria-, custom): 매 dev warning. ### 매 Debug 도구 - **Next.js**: `?_rsc_no_cache` 로 매 server payload 확인. `next dev` 의 매 colored diff. - **React DevTools**: Profiler — hydration milestone. - **Browser**: 매 view-source vs DOM inspect 비교. ### 매 응용 1. e-commerce — 매 product price (locale), cart count (cookie). 2. Auth — 매 logged-in / logged-out 분기. 3. A/B test — 매 server vs client variant 의 매 일치. ## 💻 패턴 ### Detect mismatch source (Next.js 15) ```tsx // Bad — Date.now() differs function Now() { return {Date.now()}; } // Good — pass server time as prop, render same on both function Now({ serverTime }: { serverTime: number }) { return {new Date(serverTime).toISOString()}; } ``` ### useEffect for client-only side effects ```tsx function ClientGreeting() { const [name, setName] = useState(null); useEffect(() => setName(localStorage.getItem('name')), []); if (!name) return null; // server returns null, client fills after mount return

Hi {name}

; } ``` ### dynamic() with ssr: false (Next.js) ```tsx import dynamic from 'next/dynamic'; const Chart = dynamic(() => import('./Chart'), { ssr: false }); // Renders nothing on server, only on client ``` ### Avoid Math.random in render ```tsx // Bad function Card() { return
...
; } // Good — useId function Card() { const id = useId(); return
...
; } ``` ### suppressHydrationWarning (last resort) ```tsx ``` ### Diagnose third-party DOM mutation ```tsx useEffect(() => { const obs = new MutationObserver(records => { for (const r of records) { console.warn('mutation', r.target, r.attributeName, r.addedNodes); } }); obs.observe(document.body, { subtree: true, childList: true, attributes: true }); return () => obs.disconnect(); }, []); ``` ### Locale-stable rendering ```tsx // Server and client must agree — pass timezone explicitly const fmt = new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul' }); return {fmt.format(new Date(props.iso))}; ``` ### Replay server HTML offline ```bash # Capture server HTML curl -s https://app.example.com/page > server.html # Open in fresh browser, compare with hydrated DOM via outerHTML diff ``` ### React 19 onRecoverableError ```tsx hydrateRoot(document, , { onRecoverableError(err, info) { fetch('/log/hydration-error', { method: 'POST', body: JSON.stringify({ msg: String(err), digest: info.digest }), }); }, }); ``` ## 매 결정 기준 | 원인 | Fix | |---|---| | 매 time / random | useId / passed-down server value | | 매 locale / TZ | 매 explicit Intl 의 매 timeZone | | 매 window / localStorage | 매 useEffect 후 setState | | 매 SSR 무가치 component | 매 dynamic ssr:false | | 매 third-party inject | 매 suppressHydrationWarning + 매 root 외부 | | 매 extension (Grammarly) | 매 무시 — 매 알려진 false positive | **기본값**: 매 server / client 를 매 동일 input 으로 매 강제. 매 last resort suppressHydrationWarning. ## 🔗 Graph - 부모: [[Hydration]] · [[SSR]] - 변형: [[Selective Hydration]] · [[Streaming SSR]] - 응용: [[Remix]] · [[Astro]] - Adjacent: [[useEffect]] ## 🤖 LLM 활용 **언제**: 매 hydration error stack 분석, 매 SSR 코드 review, 매 timezone bug 진단. **언제 X**: 매 pure CSR — 매 hydration 자체 X. ## ❌ 안티패턴 - **`typeof window !== 'undefined'` in render**: 매 server / client output 차이 — 매 mismatch 보장. - **Date.now() in render**: 매 항상 mismatch. - **suppressHydrationWarning 남발**: 매 진짜 bug 숨김. - **try/catch around hydrateRoot 만**: 매 root cause 추적 X. ## 🧪 검증 / 중복 - Verified (React 19 docs — Hydration Errors, Next.js 15 docs). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — 매 mismatch 원인 + Next.js 15 debug |