코드 실행 패턴
이 가이드에서는 TanStack Start 애플리케이션에서 코드가 실행되는 위치를 서버 전용, 클라이언트 전용 또는 동형(두 환경 모두)으로 제어하는 패턴을 다룹니다. 기본 개념은 실행 모델 가이드를 참조합니다.
빠른 시작
TanStack Start 애플리케이션에서 실행 경계를 설정합니다:
import {
createServerFn,
createServerOnlyFn,
createClientOnlyFn,
createIsomorphicFn,
} from '@tanstack/react-start'
// Server function (RPC call)
const getUsers = createServerFn().handler(async () => {
return await db.users.findMany()
})
// Server-only utility (crashes on client)
const getSecret = createServerOnlyFn(() => process.env.API_SECRET)
// Client-only utility (crashes on server)
const saveToStorage = createClientOnlyFn((data: any) => {
localStorage.setItem('data', JSON.stringify(data))
})
// Different implementations per environment
const logger = createIsomorphicFn()
.server((msg) => console.log(`[SERVER]: ${msg}`))
.client((msg) => console.log(`[CLIENT]: ${msg}`))
구현 패턴
점진적 향상
// Component works without JS, enhanced with JS
function SearchForm() {
const [query, setQuery] = useState('')
return (
<form action="/search" method="get">
<input
name="q"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ClientOnly fallback={<button type="submit">Search</button>}>
<SearchButton onSearch={() => search(query)} />
</ClientOnly>
</form>
)
}
환경 인식형 스토리지
const storage = createIsomorphicFn()
.server((key: string) => {
// Server: File-based cache
const fs = require('node:fs')
return JSON.parse(fs.readFileSync('.cache', 'utf-8'))[key]
})
.client((key: string) => {
// Client: localStorage
return JSON.parse(localStorage.getItem(key) || 'null')
})
일반적인 문제
환경 변수 노출
// ❌ Exposes to client bundle
const apiKey = process.env.SECRET_KEY
// ✅ Server-only access
const apiKey = createServerOnlyFn(() => process.env.SECRET_KEY)
잘못된 로더 가정
// ❌ Assuming loader is server-only
export const Route = createFileRoute('/users')({
loader: () => {
// This runs on BOTH server and client!
const secret = process.env.SECRET // Exposed to client
return fetch(`/api/users?key=${secret}`)
},
})
// ✅ Use server function for server-only operations
const getUsersSecurely = createServerFn().handler(() => {
const secret = process.env.SECRET // Server-only
return fetch(`/api/users?key=${secret}`)
})
export const Route = createFileRoute('/users')({
loader: () => getUsersSecurely(), // Isomorphic call to server function
})
하이드레이션 불일치
// ❌ Different content server vs client
function CurrentTime() {
return <div>{new Date().toLocaleString()}</div>
}
// ✅ Consistent rendering
function CurrentTime() {
const [time, setTime] = useState<string>()
useEffect(() => {
setTime(new Date().toLocaleString())
}, [])
return <div>{time || 'Loading...'}</div>
}
프로덕션 체크리스트
- 번들 분석: 서버 전용 코드가 클라이언트 번들에 포함되지 않았는지 확인합니다
- 환경 변수: 비밀 정보에
createServerOnlyFn()또는createServerFn()을 사용하는지 확인합니다 - 로더 로직: 로더는 서버 전용이 아니라 동형이라는 점을 기억합니다
- ClientOnly 폴백: 레이아웃 이동을 방지하도록 적절한 폴백을 제공합니다
- 오류 경계: 서버/클라이언트 실행 오류를 원활하게 처리합니다