본문으로 건너뛰기

코드 실행 패턴

이 가이드에서는 TanStack Start 애플리케이션에서 코드가 실행되는 위치를 서버 전용, 클라이언트 전용 또는 동형(두 환경 모두)으로 제어하는 패턴을 다룹니다. 기본 개념은 실행 모델 가이드를 참조합니다.

빠른 시작​

TanStack Start 애플리케이션에서 실행 경계를 설정합니다:

import {
createServerFn,
createServerOnlyFn,
createClientOnlyFn,
createIsomorphicFn,
} from '@tanstack/solid-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] = createSignal('')

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] = createSignal<string>()

createEffect(() => {
setTime(new Date().toLocaleString())
})

return <div>{time() || 'Loading...'}</div>
}

프로덕션 체크리스트​

  • 번들 분석: 서버 전용 코드가 클라이언트 번들에 포함되지 않았는지 확인합니다
  • 환경 변수: 비밀 정보에 createServerOnlyFn() 또는 createServerFn()을 사용하는지 확인합니다
  • 로더 로직: 로더는 서버 전용이 아니라 동형이라는 점을 기억합니다
  • ClientOnly 폴백: 레이아웃 이동을 방지할 수 있도록 적절한 폴백을 제공합니다
  • 오류 경계: 서버/클라이언트 실행 오류를 원활하게 처리합니다