字
字节笔记本
2026年5月3日
TanStack Start - Execution Model 执行模型
API中转
¥120
理解代码在哪里运行是构建 TanStack Start 应用的基础。本文介绍同构执行模型、执行边界和控制 API。
核心原则:默认同构
TanStack Start 中的所有代码默认都是同构的 - 除非明确限制,否则代码在服务端和客户端包中都会运行和包含。
tsx
// ✅ 在服务端和客户端上都运行
function formatPrice(price: number) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(price)
}
// ✅ Route loaders 是同构的
export const Route = createFileRoute('/products')({
loader: async () => {
// SSR 期间在服务端运行,导航期间在客户端运行
const response = await fetch('/api/products')
return response.json()
},
})Route
loader是同构的 - 它们在服务端和客户端上都运行,不仅仅在服务端。
执行边界
TanStack Start 应用在两个环境中运行:
服务端环境
- Node.js 运行时,可访问文件系统、数据库、环境变量
- SSR 期间 - 初始页面在服务端渲染
- API 请求 - 服务端函数在服务端执行
- 构建时 - 静态生成和预渲染
客户端环境
- 浏览器运行时,可访问 DOM、localStorage、用户交互
- 水合后 - 初始服务端渲染后客户端接管
- 导航 - 导航期间路由加载器在客户端运行
- 用户交互 - 事件处理、表单提交等
执行控制 API
仅服务端执行
| API | 用途 | 客户端行为 |
|---|---|---|
createServerFn() | RPC 调用、数据变更 | 向服务端发网络请求 |
createServerOnlyFn(fn) | 工具函数 | 抛出错误 |
tsx
import { createServerFn, createServerOnlyFn } from '@tanstack/react-start'
// RPC: 服务端执行,客户端可调用
const updateUser = createServerFn({ method: 'POST' })
.inputValidator((data: UserData) => data)
.handler(async ({ data }) => {
return await db.users.update(data)
})
// 工具: 仅服务端,客户端调用会崩溃
const getEnvVar = createServerOnlyFn(() => process.env.DATABASE_URL)仅客户端执行
| API | 用途 | 服务端行为 |
|---|---|---|
createClientOnlyFn(fn) | 浏览器工具 | 抛出错误 |
<ClientOnly> | 需要浏览器 API 的组件 | 渲染 fallback |
tsx
import { createClientOnlyFn } from '@tanstack/react-start'
import { ClientOnly } from '@tanstack/react-router'
// 工具: 仅客户端,服务端调用会崩溃
const saveToStorage = createClientOnlyFn((key: string, value: any) => {
localStorage.setItem(key, JSON.stringify(value))
})
// 组件: 仅在水合后渲染子元素
function Analytics() {
return (
<ClientOnly fallback={null}>
<GoogleAnalyticsScript />
</ClientOnly>
)
}useHydrated Hook
tsx
import { useHydrated } from '@tanstack/react-router'
function TimeZoneDisplay() {
const hydrated = useHydrated()
const timeZone = hydrated
? Intl.DateTimeFormat().resolvedOptions().timeZone
: 'UTC'
return <div>Your timezone: {timeZone}</div>
}行为:
- SSR 期间: 始终返回
false - 首次客户端渲染: 返回
false - 水合后: 返回
true
环境特定实现
tsx
import { createIsomorphicFn } from '@tanstack/react-start'
const getDeviceInfo = createIsomorphicFn()
.server(() => ({ type: 'server', platform: process.platform }))
.client(() => ({ type: 'client', userAgent: navigator.userAgent }))架构模式
渐进增强
tsx
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>
)
}RPC vs 直接函数调用
tsx
// createServerFn: RPC 模式 - 服务端执行,客户端可调用
const fetchUser = createServerFn().handler(async () => await db.users.find())
// 从客户端组件使用:
const user = await fetchUser() // ✅ 网络请求
// createServerOnlyFn: 客户端调用会崩溃
const getSecret = createServerOnlyFn(() => process.env.SECRET)
// 从客户端使用:
const secret = getSecret() // ❌ 抛出错误常见反模式
环境变量暴露
tsx
// ❌ 暴露到客户端 bundle
const apiKey = process.env.SECRET_KEY
// ✅ 仅服务端访问
const apiKey = createServerOnlyFn(() => process.env.SECRET_KEY)错误的 Loader 假设
tsx
// ❌ 假设 loader 仅在服务端运行
export const Route = createFileRoute('/users')({
loader: () => {
const secret = process.env.SECRET // 暴露到客户端
return fetch(`/api/users?key=${secret}`)
},
})
// ✅ 对服务端操作使用服务端函数
const getUsersSecurely = createServerFn().handler(() => {
const secret = process.env.SECRET // 仅服务端
return fetch(`/api/users?key=${secret}`)
})
export const Route = createFileRoute('/users')({
loader: () => getUsersSecurely(),
})水合不匹配
tsx
// ❌ 服务端 vs 客户端内容不同
function CurrentTime() {
return <div>{new Date().toLocaleString()}</div>
}
// ✅ 一致的渲染
function CurrentTime() {
const [time, setTime] = useState<string>()
useEffect(() => {
setTime(new Date().toLocaleString())
}, [])
return <div>{time || 'Loading...'}</div>
}安全考虑
环境变量策略
- 客户端暴露: 使用
VITE_前缀表示客户端可访问变量 - 仅服务端: 通过
createServerOnlyFn()或createServerFn()访问 - 永不暴露: 数据库 URL、API 密钥、密钥
API 速查
| API | 环境 | 用途 |
|---|---|---|
createServerFn() | Server | RPC 调用,客户端可调用 |
createServerOnlyFn() | Server | 仅服务端工具函数 |
createClientOnlyFn() | Client | 仅客户端工具函数 |
createIsomorphicFn() | Both | 环境特定实现 |
<ClientOnly> | Client | 条件渲染组件 |
useHydrated() | Both | 水合状态检测 |
分享: