start-server-core
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStart Server Core (@tanstack/start-server-core
)
@tanstack/start-server-coreStart Server Core (@tanstack/start-server-core
)
@tanstack/start-server-coreServer-side runtime for TanStack Start. Provides the request handler, request/response utilities, cookie management, and session management. All utilities are available anywhere in the call stack during a request via AsyncLocalStorage.
CRITICAL: These utilities are SERVER-ONLY. Import them from, not from the main entry point. They throw if called outside a server request context.@tanstack/<framework>-start/serverCRITICAL: Types are FULLY INFERRED. Never cast, never annotate inferred values.CRITICAL: Read cookies, headers, request URLs, and runtime environment values inside the active request. Do not capture them at module scope; edge runtimes may inject them per request, and concurrent requests must never share request-derived state.
TanStack Start 的服务端运行时。提供请求处理器、请求/响应工具、Cookie 管理和会话管理。在请求期间,可通过 AsyncLocalStorage 在调用栈中的任意位置使用所有工具。
关键:这些工具仅限服务端使用。请从导入,而不是从主入口导入。在服务端请求上下文之外调用时,它们会抛出异常。@tanstack/<framework>-start/server关键:类型会被完全推断。切勿强制转换,也切勿为推断出的值添加类型注解。关键:请在活动请求内读取 Cookie、请求头、请求 URL 和运行时环境值。不要在模块作用域中捕获这些值;边缘运行时可能会按请求注入它们,并且并发请求绝不能共享请求派生状态。
createStartHandler
createStartHandlercreateStartHandler
createStartHandlerCreates the main request handler that processes all incoming requests through three phases: server functions, server routes, then app SSR.
ts
// src/server.ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createStartHandler } from '@tanstack/react-start/server'
import { defaultStreamHandler } from '@tanstack/react-start/server'
export default createStartHandler({
handler: defaultStreamHandler,
})With asset URL transforms (CDN):
ts
export default createStartHandler({
handler: defaultStreamHandler,
transformAssets: 'https://cdn.example.com',
})创建主请求处理器,通过三个阶段处理所有传入请求:server function、server route,最后是应用 SSR。
ts
// src/server.ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createStartHandler } from '@tanstack/react-start/server'
import { defaultStreamHandler } from '@tanstack/react-start/server'
export default createStartHandler({
handler: defaultStreamHandler,
})使用资源 URL 转换(CDN)时:
ts
export default createStartHandler({
handler: defaultStreamHandler,
transformAssets: 'https://cdn.example.com',
})Request Utilities
请求工具
All imported from . Available anywhere during request handling — no parameter passing needed.
@tanstack/<framework>-start/server全部从 导入。在请求处理期间的任何位置均可使用,无需传递参数。
@tanstack/<framework>-start/serverReading Request Data
读取请求数据
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
getRequest,
getRequestHeaders,
getRequestHeader,
getRequestIP,
getRequestHost,
getRequestUrl,
getRequestProtocol,
} from '@tanstack/react-start/server'
const serverFn = createServerFn({ method: 'GET' }).handler(async () => {
const request = getRequest()
const headers = getRequestHeaders()
const auth = getRequestHeader('authorization')
const ip = getRequestIP({ xForwardedFor: true })
const host = getRequestHost()
const url = getRequestUrl()
const protocol = getRequestProtocol()
return { ip, host }
})ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
getRequest,
getRequestHeaders,
getRequestHeader,
getRequestIP,
getRequestHost,
getRequestUrl,
getRequestProtocol,
} from '@tanstack/react-start/server'
const serverFn = createServerFn({ method: 'GET' }).handler(async () => {
const request = getRequest()
const headers = getRequestHeaders()
const auth = getRequestHeader('authorization')
const ip = getRequestIP({ xForwardedFor: true })
const host = getRequestHost()
const url = getRequestUrl()
const protocol = getRequestProtocol()
return { ip, host }
})Setting Response Data
设置响应数据
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
setResponseHeader,
setResponseHeaders,
setResponseStatus,
getResponseHeaders,
getResponseHeader,
getResponseStatus,
removeResponseHeader,
clearResponseHeaders,
} from '@tanstack/react-start/server'
const serverFn = createServerFn({ method: 'POST' }).handler(async () => {
setResponseStatus(201)
setResponseHeader('x-custom', 'value')
setResponseHeaders({ 'cache-control': 'no-store' })
return { created: true }
})ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
setResponseHeader,
setResponseHeaders,
setResponseStatus,
getResponseHeaders,
getResponseHeader,
getResponseStatus,
removeResponseHeader,
clearResponseHeaders,
} from '@tanstack/react-start/server'
const serverFn = createServerFn({ method: 'POST' }).handler(async () => {
setResponseStatus(201)
setResponseHeader('x-custom', 'value')
setResponseHeaders({ 'cache-control': 'no-store' })
return { created: true }
})Cookie Management
Cookie 管理
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
getCookies,
getCookie,
setCookie,
deleteCookie,
} from '@tanstack/react-start/server'
const serverFn = createServerFn({ method: 'POST' }).handler(async () => {
const allCookies = getCookies()
const token = getCookie('session-token')
setCookie('preference', 'dark', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 30, // 30 days
path: '/',
})
deleteCookie('old-cookie')
})ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
getCookies,
getCookie,
setCookie,
deleteCookie,
} from '@tanstack/react-start/server'
const serverFn = createServerFn({ method: 'POST' }).handler(async () => {
const allCookies = getCookies()
const token = getCookie('session-token')
setCookie('preference', 'dark', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 30, // 30 days
path: '/',
})
deleteCookie('old-cookie')
})Session Management
会话管理
Encrypted sessions stored in cookies. Requires a password for encryption.
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
useSession,
getSession,
updateSession,
clearSession,
} from '@tanstack/react-start/server'
type SessionData = {
userId?: string
}
function getSessionConfig() {
const password = process.env.SESSION_SECRET
if (!password || password.length < 32) {
throw new Error('SESSION_SECRET must be at least 32 characters')
}
return {
password,
name: 'my-app-session',
maxAge: 60 * 60 * 24 * 7,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
path: '/',
},
}
}
function getDummyPasswordHash() {
// Precompute this with the same algorithm and cost as real password hashes.
const hash = process.env.DUMMY_PASSWORD_HASH
if (!hash) {
throw new Error('DUMMY_PASSWORD_HASH is required')
}
return hash
}
// Full session manager
const getUser = createServerFn({ method: 'GET' }).handler(async () => {
const session = await useSession<SessionData>(getSessionConfig())
if (!session.data.userId) {
return null
}
return db.users.findById(session.data.userId)
})
// Update session
const login = createServerFn({ method: 'POST' })
.validator((data: unknown) => {
if (
typeof data !== 'object' ||
data === null ||
!('email' in data) ||
typeof data.email !== 'string' ||
data.email.trim().length === 0 ||
!('password' in data) ||
typeof data.password !== 'string' ||
data.password.length === 0
) {
throw new Error('Invalid credentials')
}
return {
email: data.email.trim().toLowerCase(),
password: data.password,
}
})
.handler(async ({ data }) => {
const user = await db.users.findByEmail(data.email)
const passwordHash = user?.passwordHash ?? getDummyPasswordHash()
const passwordMatches = await verifyPassword(data.password, passwordHash)
if (!user || !passwordMatches) {
throw new Error('Invalid credentials')
}
await updateSession<SessionData>(getSessionConfig(), {
userId: user.id,
})
return { success: true }
})
// Clear session
const logout = createServerFn({ method: 'POST' }).handler(async () => {
await clearSession(getSessionConfig())
return { success: true }
})存储在 Cookie 中的加密会话。加密需要提供密码。
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import {
useSession,
getSession,
updateSession,
clearSession,
} from '@tanstack/react-start/server'
type SessionData = {
userId?: string
}
function getSessionConfig() {
const password = process.env.SESSION_SECRET
if (!password || password.length < 32) {
throw new Error('SESSION_SECRET must be at least 32 characters')
}
return {
password,
name: 'my-app-session',
maxAge: 60 * 60 * 24 * 7,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
path: '/',
},
}
}
function getDummyPasswordHash() {
// Precompute this with the same algorithm and cost as real password hashes.
const hash = process.env.DUMMY_PASSWORD_HASH
if (!hash) {
throw new Error('DUMMY_PASSWORD_HASH is required')
}
return hash
}
// Full session manager
const getUser = createServerFn({ method: 'GET' }).handler(async () => {
const session = await useSession<SessionData>(getSessionConfig())
if (!session.data.userId) {
return null
}
return db.users.findById(session.data.userId)
})
// Update session
const login = createServerFn({ method: 'POST' })
.validator((data: unknown) => {
if (
typeof data !== 'object' ||
data === null ||
!('email' in data) ||
typeof data.email !== 'string' ||
data.email.trim().length === 0 ||
!('password' in data) ||
typeof data.password !== 'string' ||
data.password.length === 0
) {
throw new Error('Invalid credentials')
}
return {
email: data.email.trim().toLowerCase(),
password: data.password,
}
})
.handler(async ({ data }) => {
const user = await db.users.findByEmail(data.email)
const passwordHash = user?.passwordHash ?? getDummyPasswordHash()
const passwordMatches = await verifyPassword(data.password, passwordHash)
if (!user || !passwordMatches) {
throw new Error('Invalid credentials')
}
await updateSession<SessionData>(getSessionConfig(), {
userId: user.id,
})
return { success: true }
})
// Clear session
const logout = createServerFn({ method: 'POST' }).handler(async () => {
await clearSession(getSessionConfig())
return { success: true }
})Session Config
会话配置
| Option | Type | Default | Description |
|---|---|---|---|
| | required | Encryption key |
| | | Cookie name |
| | | Expiry in seconds |
| | | Cookie settings |
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| | 必填 | 加密密钥 |
| | | Cookie 名称 |
| | | 过期时间(秒) |
| | | Cookie 设置 |
Session Manager Methods
会话管理器方法
ts
const session = await useSession<{ userId: string }>(config)
session.id // Session ID (string | undefined)
session.data // Session data (typed)
await session.update({ userId: '123' }) // Persist session data
await session.clear() // Clear session datats
const session = await useSession<{ userId: string }>(config)
session.id // Session ID (string | undefined)
session.data // Session data (typed)
await session.update({ userId: '123' }) // Persist session data
await session.clear() // Clear session dataProduction Session Rules
生产环境会话规则
- Keep cookie session data small and non-sensitive. Store a stable session or user ID, then load current permissions and account state from the authoritative store on each protected request.
- Use a server-side session record when you need revocation, device tracking, large data, or immediate role changes. Put only its opaque ID in the cookie.
- Rotate the session after login, privilege changes, password changes, and logout.
- Use ,
HttpOnly,SameSite, andPath=/in production. Use aSecurecookie name in production only when__Host-, noSecure, andDomainare all enforced.Path=/ - Use the same cookie name and path when clearing a session. Test login, authenticated refresh, expiry, logout, and a replay of the old cookie.
- 保持 Cookie 中的会话数据小巧且不敏感。存储稳定的会话 ID 或用户 ID,然后在每个受保护的请求中从权威数据源加载当前权限和账户状态。
- 当需要撤销、设备跟踪、大量数据或即时角色变更时,请使用服务端会话记录。Cookie 中只应存放其不透明 ID。
- 登录、权限变更、密码变更和退出登录后轮换会话。
- 在生产环境中使用 、
HttpOnly、SameSite和Path=/。仅当同时强制设置Secure、不使用Secure且使用Domain时,才在生产环境中使用Path=/Cookie 名称。__Host- - 清除会话时使用相同的 Cookie 名称和路径。测试登录、认证后的刷新、过期、退出登录以及重放旧 Cookie。
Query Validation
查询参数验证
Validate query string parameters using a Standard Schema:
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { getValidatedQuery } from '@tanstack/react-start/server'
import { z } from 'zod'
const serverFn = createServerFn({ method: 'GET' }).handler(async () => {
const query = await getValidatedQuery(
z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().default(20),
}),
)
return { page: query.page }
})Note:accepts a Standard Schema validator, not a callback function.getValidatedQuery
使用 Standard Schema 验证查询字符串参数:
ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { getValidatedQuery } from '@tanstack/react-start/server'
import { z } from 'zod'
const serverFn = createServerFn({ method: 'GET' }).handler(async () => {
const query = await getValidatedQuery(
z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().default(20),
}),
)
return { page: query.page }
})注意:接受 Standard Schema 验证器,而不是回调函数。getValidatedQuery
How Request Handling Works
请求处理的工作方式
createStartHandler-
Server Function Dispatch — If URL matches the server function prefix (), deserializes the payload, runs global request middleware, executes the server function, and returns the serialized result.
/_serverFn -
Server Route Handler — For non-server-function requests, matches the URL against routes with. Runs route middleware, then the matched HTTP method handler. Handlers can return a
server.handlersor callResponseto fall through to SSR.next() -
App Router SSR — Loads all route loaders, dehydrates state for client hydration, and calls the handler callback (e.g.,) to render HTML.
defaultStreamHandler
createStartHandler-
Server Function 分发 — 如果 URL 与 server function 前缀()匹配,则反序列化载荷、执行全局请求中间件、运行 server function,并返回序列化结果。
/_serverFn -
Server Route Handler — 对于非 server function 请求,使用将 URL 匹配到相应路由。执行路由中间件,然后运行匹配的 HTTP method handler。Handler 可以返回
server.handlers,也可以调用Response继续进入 SSR。next() -
App Router SSR — 加载所有 route loader,对状态进行脱水以供客户端 hydration,然后调用 handler 回调(例如)渲染 HTML。
defaultStreamHandler
Common Mistakes
常见错误
1. CRITICAL: Importing server utilities in client code
1. 关键:在客户端代码中导入服务端工具
Server utilities use AsyncLocalStorage and only work during server request handling. Importing them in client code causes build errors or runtime crashes.
ts
// WRONG — importing in a component file that runs on client
import { getCookie } from '@tanstack/react-start/server'
function MyComponent() {
const token = getCookie('auth') // crashes on client
}
// CORRECT — use inside server functions only
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import { getCookie } from '@tanstack/react-start/server'
const getAuth = createServerFn({ method: 'GET' }).handler(async () => {
return getCookie('auth')
})服务端工具使用 AsyncLocalStorage,仅在服务端请求处理期间有效。在客户端代码中导入它们会导致构建错误或运行时崩溃。
ts
// WRONG — importing in a component file that runs on client
import { getCookie } from '@tanstack/react-start/server'
function MyComponent() {
const token = getCookie('auth') // crashes on client
}
// CORRECT — use inside server functions only
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
import { getCookie } from '@tanstack/react-start/server'
const getAuth = createServerFn({ method: 'GET' }).handler(async () => {
return getCookie('auth')
})2. HIGH: Forgetting session password for most session operations
2. 高危:大多数会话操作忘记提供会话密码
useSessiongetSessionupdateSessionsealSessionpasswordclearSessionPartial<SessionConfig>useSessiongetSessionupdateSessionsealSessionpasswordclearSessionPartial<SessionConfig>3. MEDIUM: Using session without HTTPS in production
3. 中危:在生产环境中未使用 HTTPS 即使用会话
Session cookies should use in production. The default cookie options may not enforce this.
secure: true生产环境中的会话 Cookie 应使用 。默认 Cookie 选项可能不会强制执行此设置。
secure: true4. CRITICAL: Capturing request or environment state at module scope
4. 关键:在模块作用域中捕获请求或环境状态
Do not create session config from at module load or cache , headers, cookies, or session data in a module variable. Create config and read request state inside the handler or middleware callback. This is required for per-request edge environments and prevents cross-request data leaks.
process.envgetRequest()不要在模块加载时根据 创建会话配置,也不要在模块变量中缓存 、请求头、Cookie 或会话数据。请在 handler 或中间件回调内部创建配置并读取请求状态。这是按请求运行的边缘环境所必需的,并且可以防止跨请求数据泄露。
process.envgetRequest()Cross-References
相关文档
- start-core/server-functions — creating server functions that use these utilities
- start-core/middleware — request middleware
- start-core/server-routes — server route handlers
- start-core/server-functions — 创建使用这些工具的 server function
- start-core/middleware — 请求中间件
- start-core/server-routes — server route handler