start-server-core

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Start Server Core (
@tanstack/start-server-core
)

Start Server Core (
@tanstack/start-server-core
)

Server-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
@tanstack/<framework>-start/server
, not from the main entry point. They throw if called outside a server request context.
CRITICAL: 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

createStartHandler

Creates 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
@tanstack/<framework>-start/server
. Available anywhere during request handling — no parameter passing needed.
全部从
@tanstack/<framework>-start/server
导入。在请求处理期间的任何位置均可使用,无需传递参数。

Reading 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

会话配置

OptionTypeDefaultDescription
password
string
requiredEncryption key
name
string
'start'
Cookie name
maxAge
number
undefined
Expiry in seconds
cookie
false | CookieOptions
undefined
Cookie settings
选项类型默认值说明
password
string
必填加密密钥
name
string
'start'
Cookie 名称
maxAge
number
undefined
过期时间(秒)
cookie
false | CookieOptions
undefined
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 data
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 data

Production 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
    ,
    Path=/
    , and
    Secure
    in production. Use a
    __Host-
    cookie name in production only when
    Secure
    , no
    Domain
    , and
    Path=/
    are all enforced.
  • 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=/
    时,才在生产环境中使用
    __Host-
    Cookie 名称。
  • 清除会话时使用相同的 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:
getValidatedQuery
accepts a Standard Schema validator, not a callback function.
使用 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 }
})
注意:
getValidatedQuery
接受 Standard Schema 验证器,而不是回调函数。

How Request Handling Works

请求处理的工作方式

createStartHandler
processes requests in three phases:
  1. Server Function Dispatch — If URL matches the server function prefix (
    /_serverFn
    ), deserializes the payload, runs global request middleware, executes the server function, and returns the serialized result.
  2. Server Route Handler — For non-server-function requests, matches the URL against routes with
    server.handlers
    . Runs route middleware, then the matched HTTP method handler. Handlers can return a
    Response
    or call
    next()
    to fall through to SSR.
  3. App Router SSR — Loads all route loaders, dehydrates state for client hydration, and calls the handler callback (e.g.,
    defaultStreamHandler
    ) to render HTML.
createStartHandler
通过三个阶段处理请求:
  1. Server Function 分发 — 如果 URL 与 server function 前缀(
    /_serverFn
    )匹配,则反序列化载荷、执行全局请求中间件、运行 server function,并返回序列化结果。
  2. Server Route Handler — 对于非 server function 请求,使用
    server.handlers
    将 URL 匹配到相应路由。执行路由中间件,然后运行匹配的 HTTP method handler。Handler 可以返回
    Response
    ,也可以调用
    next()
    继续进入 SSR。
  3. App Router SSR — 加载所有 route loader,对状态进行脱水以供客户端 hydration,然后调用 handler 回调(例如
    defaultStreamHandler
    )渲染 HTML。

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. 高危:大多数会话操作忘记提供会话密码

useSession
,
getSession
,
updateSession
, and
sealSession
all require a
password
field for encryption. Missing it throws at runtime.
clearSession
accepts
Partial<SessionConfig>
, so password is optional for clearing.
useSession
、
getSession
、
updateSession
和
sealSession
都需要
password
字段进行加密。缺少该字段会在运行时抛出异常。
clearSession
接受
Partial<SessionConfig>
,因此清除会话时可以不提供密码。

3. MEDIUM: Using session without HTTPS in production

3. 中危:在生产环境中未使用 HTTPS 即使用会话

Session cookies should use
secure: true
in production. The default cookie options may not enforce this.
生产环境中的会话 Cookie 应使用
secure: true
。默认 Cookie 选项可能不会强制执行此设置。

4. CRITICAL: Capturing request or environment state at module scope

4. 关键:在模块作用域中捕获请求或环境状态

Do not create session config from
process.env
at module load or cache
getRequest()
, 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.env
创建会话配置,也不要在模块变量中缓存
getRequest()
、请求头、Cookie 或会话数据。请在 handler 或中间件回调内部创建配置并读取请求状态。这是按请求运行的边缘环境所必需的,并且可以防止跨请求数据泄露。

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