하이드레이션 오류
발생하는 이유
- 불일치: 하이드레이션 중 서버 HTML이 클라이언트 렌더링 결과와 다릅니다
- 일반적인 원인:
Intl(로케일/시간대),Date.now(), 무작위 ID, 반응형 전용 로직, 기능 플래그, 사용자 환경설정
전략 1 — 서버와 클라이언트를 일치시킵니다
- 서버에서 결정론적인 로케일/시간대를 선택하고 클라이언트에서도 동일하게 사용합니다
- 신뢰할 수 있는 원본: 쿠키(권장) 또는
Accept-Language헤더 - 서버에서 한 번만 계산하고 초기 상태로 하이드레이션합니다
// src/start.ts
import { createStart, createMiddleware } from '@tanstack/solid-start'
import {
getRequestHeader,
getCookie,
setCookie,
} from '@tanstack/solid-start/server'
const localeTzMiddleware = createMiddleware().server(async ({ next }) => {
const header = getRequestHeader('accept-language')
const headerLocale = header?.split(',')[0] || 'en-US'
const cookieLocale = getCookie('locale')
const cookieTz = getCookie('tz') // set by client later (see Strategy 2)
const locale = cookieLocale || headerLocale
const timeZone = cookieTz || 'UTC'
setCookie('locale', locale, { path: '/', maxAge: 60 * 60 * 24 * 365 })
return next({ context: { locale, timeZone } })
})
export const startInstance = createStart(() => ({
requestMiddleware: [localeTzMiddleware],
}))
// src/routes/index.tsx (example)
import { createFileRoute } from '@tanstack/solid-router'
import { createServerFn } from '@tanstack/solid-start'
import { getCookie } from '@tanstack/solid-start/server'
export const getServerNow = createServerFn().handler(async () => {
const locale = getCookie('locale') || 'en-US'
const timeZone = getCookie('tz') || 'UTC'
return new Intl.DateTimeFormat(locale, {
dateStyle: 'medium',
timeStyle: 'short',
timeZone,
}).format(new Date())
})
export const Route = createFileRoute('/')({
loader: () => getServerNow(),
component: () => {
const serverNow = Route.useLoaderData() as string
return <time dateTime={serverNow}>{serverNow}</time>
},
})
전략 2 — 클라이언트가 환경을 알려주도록 합니다
- 첫 방문 시 클라이언트 시간대가 포함된 쿠키를 설정하며, 그전까지 SSR은
UTC을 사용합니다
import { ClientOnly } from '@tanstack/solid-router'
function SetTimeZoneCookie() {
if (typeof window !== 'undefined') {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
document.cookie = `tz=${tz}; path=/; max-age=31536000`
}
return null
}
;<ClientOnly fallback={null}>
<SetTimeZoneCookie />
</ClientOnly>
전략 3 — 클라이언트 전용으로 만듭니다
- SSR/하이드레이션을 피하려면
<ClientOnly>또는 Solid의<NoHydration>을 사용합니다
import { ClientOnly } from '@tanstack/solid-router'
import { NoHydration } from 'solid-js/web'
<ClientOnly fallback={<span>—</span>}>
<RelativeTime ts={someTs} />
</ClientOnly>
<NoHydration>
<time>{new Date().toLocaleString()}</time>
</NoHydration>
전략 4 — 라우트의 SSR을 비활성화하거나 제한합니다
- 서버에서 컴포넌트를 렌더링하지 않으려면 선택적 SSR을 사용합니다
export const Route = createFileRoute('/unstable')({
ssr: 'data-only', // or false
component: () => <ExpensiveViz />,
})
전략 5 — 최후의 수단으로 억제합니다
- 위 도구를 우선 사용하고, 불일치를 숨기기보다 방지합니다
체크리스트
- 결정론적 입력: 로케일, 시간대, 기능 플래그
- 클라이언트 컨텍스트에는 쿠키를 우선 사용하고,
Accept-Language을 대안으로 사용합니다 - 본질적으로 동적인 UI에는
<ClientOnly>/<NoHydration>을 사용합니다 - 서버 HTML을 안정적으로 유지할 수 없으면 선택적 SSR을 사용합니다