tanstack-start

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TanStack Start Skill

TanStack Start 技能指南

⚠️ Status: Production Ready (RC v1.154.0)
TanStack Start is a full-stack React framework built on TanStack Router. It provides type-safe routing, server functions, SSR/streaming, and first-class Cloudflare Workers support.
Current Package:
@tanstack/react-start@1.154.0
(Jan 21, 2026)
Production Readiness:
  • ✅ RC v1.154.0 stable (v1.0 expected soon)
  • ✅ Memory leak issue (#5734) resolved Jan 5, 2026
  • ✅ Migrated to Vite from Vinxi (v1.121.0, June 2025)
  • ✅ Production deployments on Cloudflare Workers validated
This skill prevents 9 documented errors and provides comprehensive guidance for Cloudflare Workers deployment, migrations, and server function patterns.

⚠️ 状态:已就绪可用于生产环境(RC v1.154.0)
TanStack Start 是基于TanStack Router构建的全栈React框架。它提供类型安全路由、服务器函数、SSR/流式渲染,以及一流的Cloudflare Workers支持。
当前包版本:
@tanstack/react-start@1.154.0
(2026年1月21日)
生产环境就绪状态:
  • ✅ RC v1.154.0 稳定版(v1.0 正式版即将发布)
  • ✅ 内存泄漏问题(#5734)已于2026年1月5日修复
  • ✅ 已从Vinxi迁移到Vite(v1.121.0,2025年6月)
  • ✅ 已验证在Cloudflare Workers上的生产环境部署
本技能指南可预防9种已记录的错误,并为Cloudflare Workers部署、迁移和服务器函数模式提供全面指导。

Table of Contents

目录

Quick Start

快速开始

Installation

安装步骤

bash
undefined
bash
undefined

Create new project (uses Vite)

创建新项目(使用Vite)

npm create cloudflare@latest my-app -- --framework=tanstack-start cd my-app
npm create cloudflare@latest my-app -- --framework=tanstack-start cd my-app

Install dependencies

安装依赖

npm install
npm install

Development

开发模式

npm run dev
npm run dev

Build and deploy

构建并部署

npm run build wrangler deploy
undefined
npm run build wrangler deploy
undefined

Dependencies

依赖配置

json
{
  "dependencies": {
    "@tanstack/react-start": "^1.154.0",
    "@tanstack/react-router": "latest",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "vite": "latest",
    "@cloudflare/vite-plugin": "latest",
    "wrangler": "latest"
  }
}

json
{
  "dependencies": {
    "@tanstack/react-start": "^1.154.0",
    "@tanstack/react-router": "latest",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "vite": "latest",
    "@cloudflare/vite-plugin": "latest",
    "wrangler": "latest"
  }
}

Migration from Vinxi to Vite (v1.121.0+)

从Vinxi迁移到Vite(v1.121.0+)

Timeline: TanStack Start migrated from Vinxi to Vite in v1.121.0 (released June 10, 2025).
时间线:TanStack Start在v1.121.0版本(2025年6月10日发布)中从Vinxi迁移到Vite。

Breaking Changes

破坏性变更

ChangeOld (Vinxi)New (Vite)
Package name
@tanstack/start
@tanstack/react-start
Config file
app.config.ts
vite.config.ts
API routes
createAPIFileRoute()
createServerFileRoute().methods()
Entry files
ssr.tsx
,
client.tsx
server.tsx
(optional)
Source folder
app/
src/
Dev command
vinxi dev
vite dev
变更项旧版(Vinxi)新版(Vite)
包名称
@tanstack/start
@tanstack/react-start
配置文件
app.config.ts
vite.config.ts
API路由
createAPIFileRoute()
createServerFileRoute().methods()
入口文件
ssr.tsx
,
client.tsx
server.tsx
(可选)
源码目录
app/
src/
开发命令
vinxi dev
vite dev

Migration Steps

迁移步骤

bash
undefined
bash
undefined

1. Remove Vinxi

1. 移除Vinxi

npm uninstall vinxi @tanstack/start
npm uninstall vinxi @tanstack/start

2. Install Vite and framework-specific adapter

2. 安装Vite和框架适配插件

npm install vite @tanstack/react-start @cloudflare/vite-plugin
npm install vite @tanstack/react-start @cloudflare/vite-plugin

3. Delete old config

3. 删除旧配置文件

rm app.config.ts
rm app.config.ts

4. Delete default entry files (unless customized)

4. 删除默认入口文件(如果未自定义)

rm app/ssr.tsx app/client.tsx
rm app/ssr.tsx app/client.tsx

5. Rename customized entries

5. 重命名自定义入口文件

mv app/ssr.tsx app/server.tsx # If you customized SSR entry
mv app/ssr.tsx app/server.tsx # 如果您自定义了SSR入口

6. Move source files (optional, for consistency)

6. 移动源码文件(可选,为了保持一致性)

mv app/ src/
undefined
mv app/ src/
undefined

Create vite.config.ts

创建vite.config.ts

typescript
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'

export default defineConfig({
  plugins: [
    tanstackStart(),
    cloudflare({
      viteEnvironment: { name: 'ssr' } // Required for Workers
    })
  ]
})
typescript
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'

export default defineConfig({
  plugins: [
    tanstackStart(),
    cloudflare({
      viteEnvironment: { name: 'ssr' } // Workers环境必填
    })
  ]
})

Update package.json Scripts

更新package.json脚本

json
{
  "scripts": {
    "dev": "vite dev --port 3000",
    "build": "vite build",
    "start": "node .output/server/index.mjs"
  }
}
json
{
  "scripts": {
    "dev": "vite dev --port 3000",
    "build": "vite build",
    "start": "node .output/server/index.mjs"
  }
}

Update API Routes

更新API路由

typescript
// Old (Vinxi)
import { createAPIFileRoute } from '@tanstack/start/api'

export const Route = createAPIFileRoute('/api/users')({
  GET: async () => {
    return { users: [] }
  }
})

// New (Vite)
import { createServerFileRoute } from '@tanstack/react-start/api'

export const Route = createServerFileRoute('/api/users').methods({
  GET: async () => {
    return { users: [] }
  }
})
typescript
// 旧版(Vinxi)
import { createAPIFileRoute } from '@tanstack/start/api'

export const Route = createAPIFileRoute('/api/users')({
  GET: async () => {
    return { users: [] }
  }
})

// 新版(Vite)
import { createServerFileRoute } from '@tanstack/react-start/api'

export const Route = createServerFileRoute('/api/users').methods({
  GET: async () => {
    return { users: [] }
  }
})

Common Migration Errors

常见迁移错误

Error: "invariant failed: could not find the nearest match" Cause: Old Vinxi route definitions mixed with Vite config Fix: Update all
createAPIFileRoute()
createServerFileRoute().methods()
Error: "SyntaxError: The requested module '@tanstack/router-generator' does not provide an export named 'CONSTANTS'" Cause: Conflicting Vinxi/Vite dependencies Fix: Delete
node_modules/
,
package-lock.json
, reinstall
Issue: Auto-generated
app.config.timestamp_*
files duplicating Cause: Old Vinxi config interfering Fix: Delete all
app.config.*
files, restart dev server

错误:"invariant failed: could not find the nearest match" 原因:旧版Vinxi路由定义与Vite配置混合使用 修复:将所有
createAPIFileRoute()
替换为
createServerFileRoute().methods()
错误:"SyntaxError: The requested module '@tanstack/router-generator' does not provide an export named 'CONSTANTS'" 原因:Vinxi/Vite依赖冲突 修复:删除
node_modules/
package-lock.json
,重新安装依赖
问题:自动生成的
app.config.timestamp_*
文件重复 原因:旧版Vinxi配置干扰 修复:删除所有
app.config.*
文件,重启开发服务器

Cloudflare Workers Deployment

Cloudflare Workers部署

Required Configuration

必要配置

wrangler.toml (or wrangler.jsonc)

wrangler.toml(或wrangler.jsonc)

toml
name = "my-app"
compatibility_date = "2026-01-21"
compatibility_flags = ["nodejs_compat"] # REQUIRED
toml
name = "my-app"
compatibility_date = "2026-01-21"
compatibility_flags = ["nodejs_compat"] # 必填

REQUIRED: Point to TanStack Start's server entry

必填:指向TanStack Start的服务器入口

main = "@tanstack/react-start/server-entry"
[observability] enabled = true # Optional: Enable monitoring
undefined
main = "@tanstack/react-start/server-entry"
[observability] enabled = true # 可选:启用监控
undefined

vite.config.ts

vite.config.ts

typescript
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'

export default defineConfig({
  plugins: [
    tanstackStart(),
    cloudflare({
      viteEnvironment: { name: 'ssr' } // REQUIRED
    })
  ]
})
typescript
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'

export default defineConfig({
  plugins: [
    tanstackStart(),
    cloudflare({
      viteEnvironment: { name: 'ssr' } // 必填
    })
  ]
})

Bindings (D1, KV, R2)

绑定(D1、KV、R2)

toml
undefined
toml
undefined

D1 Database

D1数据库

[[d1_databases]] binding = "DB" database_name = "my-database" database_id = "your-database-id"
[[d1_databases]] binding = "DB" database_name = "my-database" database_id = "your-database-id"

KV Namespace

KV命名空间

[[kv_namespaces]] binding = "KV" id = "your-kv-id"
[[kv_namespaces]] binding = "KV" id = "your-kv-id"

R2 Bucket

R2存储桶

[[r2_buckets]] binding = "BUCKET" bucket_name = "my-bucket"

Access bindings in server functions:

```typescript
import { createServerFn } from '@tanstack/react-start/server'

export const getUser = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env

    // D1
    const result = await env.DB.prepare('SELECT * FROM users').all()

    // KV
    const value = await env.KV.get('key')

    // R2
    const object = await env.BUCKET.get('file.txt')

    return result.results
  })
[[r2_buckets]] binding = "BUCKET" bucket_name = "my-bucket"

在服务器函数中访问绑定:

```typescript
import { createServerFn } from '@tanstack/react-start/server'

export const getUser = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env

    // D1操作
    const result = await env.DB.prepare('SELECT * FROM users').all()

    // KV操作
    const value = await env.KV.get('key')

    // R2操作
    const object = await env.BUCKET.get('file.txt')

    return result.results
  })

Prerendering Gotchas

预渲染注意事项

Critical: Prerendering runs during build step using LOCAL environment variables, not Cloudflare bindings.
Problem: If routes use
loaders
that query D1/KV/R2, prerendering will fail because bindings aren't available at build time.
Solutions:
  1. Disable prerendering for routes with bindings:
typescript
export const Route = createFileRoute('/users')({
  loader: async () => {
    // This route queries D1
  },
  // Disable prerendering
  prerender: false
})
  1. Use remote bindings during builds (requires
    wrangler dev
    running):
bash
undefined
关键提示:预渲染在构建阶段使用本地环境变量运行,而非Cloudflare绑定。
问题:如果路由的
loaders
查询D1/KV/R2,预渲染会失败,因为构建时无法访问绑定。
解决方案
  1. 对使用绑定的路由禁用预渲染
typescript
export const Route = createFileRoute('/users')({
  loader: async () => {
    // 该路由查询D1
  },
  // 禁用预渲染
  prerender: false
})
  1. 构建时使用远程绑定(需要
    wrangler dev
    运行):
bash
undefined

In CI environment

在CI环境中

export CLOUDFLARE_INCLUDE_PROCESS_ENV=true
export CLOUDFLARE_INCLUDE_PROCESS_ENV=true

Use .env file (NOT .env.local) for CI

为CI环境使用.env文件(不要用.env.local)

.env.local is gitignored and won't be in CI

.env.local已被git忽略,不会出现在CI中


3. **Conditional logic in loaders**:

```typescript
loader: async ({ context }) => {
  // Skip DB queries during prerender
  if (typeof context.cloudflare === 'undefined') {
    return { users: [] }
  }

  const result = await context.cloudflare.env.DB.prepare('SELECT * FROM users').all()
  return { users: result.results }
}
Version Requirements:
  • Static prerendering requires
    @tanstack/react-start@1.138.0+


3. **在loaders中使用条件逻辑**:

```typescript
loader: async ({ context }) => {
  // 预渲染时跳过数据库查询
  if (typeof context.cloudflare === 'undefined') {
    return { users: [] }
  }

  const result = await context.cloudflare.env.DB.prepare('SELECT * FROM users').all()
  return { users: result.results }
}
版本要求
  • 静态预渲染需要
    @tanstack/react-start@1.138.0+

Server Functions

服务器函数

Server functions run on the server and can access Cloudflare bindings, databases, and secrets.
服务器函数在服务器端运行,可访问Cloudflare绑定、数据库和密钥。

Basic Server Function

基础服务器函数

typescript
import { createServerFn } from '@tanstack/react-start/server'

export const getUsers = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env
    const result = await env.DB.prepare('SELECT * FROM users').all()
    return result.results
  })
typescript
import { createServerFn } from '@tanstack/react-start/server'

export const getUsers = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env
    const result = await env.DB.prepare('SELECT * FROM users').all()
    return result.results
  })

Use in Components

在组件中使用

typescript
import { getUsers } from './server-functions'

function UserList() {
  const users = await getUsers()

  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  )
}
typescript
import { getUsers } from './server-functions'

function UserList() {
  const users = await getUsers()

  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  )
}

File Upload Limitation

文件上传限制

⚠️ Known Issue: TanStack Start automatically calls
await request.formData()
for multipart/form-data requests, loading entire files into memory BEFORE the handler runs.
Impact:
  • Cannot enforce upload size limits before loading
  • Cannot implement streaming uploads
  • Large file uploads consume excessive memory
Example of the Problem:
typescript
export const uploadFile = createServerFn()
  .handler(async ({ request }) => {
    // By the time this runs, the entire file is already in memory
    const formData = await request.formData()
    const file = formData.get('file') as File

    // Too late to check size - file already loaded!
    if (file.size > 10_000_000) {
      throw new Error("File too large")
    }
  })
Workarounds:
  1. Client-side validation (not foolproof, can be bypassed):
typescript
function FileUpload() {
  const handleSubmit = async (e: FormEvent) => {
    const file = e.currentTarget.querySelector('input[type="file"]').files[0]

    if (file.size > 10_000_000) {
      alert("File too large")
      return
    }

    await uploadFile({ file })
  }

  return <form onSubmit={handleSubmit}>...</form>
}
  1. Use Cloudflare R2 multipart upload API directly for large files (bypasses Start's form handling).
Status: Open issue #5704, no fix planned yet.
⚠️ 已知问题:TanStack Start会自动对multipart/form-data请求调用
await request.formData()
,在处理程序运行前将整个文件加载到内存中。
影响
  • 无法在加载前强制执行上传大小限制
  • 无法实现流式上传
  • 大文件上传会消耗过多内存
问题示例
typescript
export const uploadFile = createServerFn()
  .handler(async ({ request }) => {
    // 当此代码运行时,整个文件已加载到内存
    const formData = await request.formData()
    const file = formData.get('file') as File

    // 此时检查大小已太晚 - 文件已加载完成!
    if (file.size > 10_000_000) {
      throw new Error("文件过大")
    }
  })
解决方法
  1. 客户端验证(不绝对安全,可被绕过):
typescript
function FileUpload() {
  const handleSubmit = async (e: FormEvent) => {
    const file = e.currentTarget.querySelector('input[type="file"]').files[0]

    if (file.size > 10_000_000) {
      alert("文件过大")
      return
    }

    await uploadFile({ file })
  }

  return <form onSubmit={handleSubmit}>...</form>
}
  1. 直接使用Cloudflare R2分块上传API处理大文件(绕过Start的表单处理)。
状态开放问题 #5704,目前暂无修复计划。

Server Function Redirects Return Undefined

服务器函数重定向返回Undefined

When a server function performs a redirect, the promise resolves to
undefined
instead of the declared return type.
typescript
const login = createServerFn<{ username: string, password: string }, User>()
  .handler(async ({ data, request }) => {
    const user = await authenticateUser(data)

    if (!user) {
      // Redirect returns void, but type says it returns User
      throw redirect({ to: '/login', status: 401 })
    }

    return user
  })

// In component
const result = await login({ username, password })
// result is undefined if redirected, User object otherwise
// Check before using!
if (result) {
  console.log(result.name)
}
Prevention: Always check return value before use if server function can redirect.
Status: Open PR #6295 to fix return type.

当服务器函数执行重定向时,Promise会解析为
undefined
而非声明的返回类型。
typescript
const login = createServerFn<{ username: string, password: string }, User>()
  .handler(async ({ data, request }) => {
    const user = await authenticateUser(data)

    if (!user) {
      // 重定向返回void,但类型声明返回User
      throw redirect({ to: '/login', status: 401 })
    }

    return user
  })

// 在组件中
const result = await login({ username, password })
// 如果重定向,result为undefined,否则为User对象
// 使用前务必检查!
if (result) {
  console.log(result.name)
}
预防措施:如果服务器函数可能重定向,使用返回值前务必检查。
状态开放PR #6295以修复返回类型。

Authentication Patterns

认证模式

Stateful Backend Integration (Laravel Sanctum, etc.)

有状态后端集成(Laravel Sanctum等)

Problem: When using stateful backends, server functions lose auth context because requests originate from the Start server, not the browser. Cookies, CSRF tokens, and origin headers are missing.
typescript
// This FAILS - cookies not forwarded
const getData = createServerFn()
  .handler(async () => {
    const response = await fetch('https://api.example.com/user')
    // 401 Unauthorized - no cookies!
  })
Solution 1: Use createIsomorphicFn (runs on client when possible)
typescript
import { createIsomorphicFn } from '@tanstack/react-start/server'

const getData = createIsomorphicFn()
  .handler(async () => {
    // Runs on client when possible, preserving cookies
    const response = await fetch('https://api.example.com/user')
    return response.json()
  })
Solution 2: Manual Header Forwarding
typescript
import { createServerFn } from '@tanstack/react-start/server'
import { getRequestHeaders } from '@tanstack/react-start/server'

const getData = createServerFn()
  .handler(async () => {
    const headers = getRequestHeaders() // Get browser's original headers

    const response = await fetch('https://api.example.com/user', {
      headers: {
        'Cookie': headers.get('cookie') || '',
        'X-XSRF-TOKEN': headers.get('x-xsrf-token') || '',
        'Origin': headers.get('origin') || '',
      }
    })

    return response.json()
  })
When to Use Each:
  • createIsomorphicFn
    : Best for read operations, maintains full browser context
  • Manual forwarding: Required for operations that must run server-side (secrets, DB access)
问题:使用有状态后端时,服务器函数会丢失认证上下文,因为请求来自Start服务器而非浏览器。Cookies、CSRF令牌和Origin头都会丢失。
typescript
// 此代码会失败 - Cookies未转发
const getData = createServerFn()
  .handler(async () => {
    const response = await fetch('https://api.example.com/user')
    // 401未授权 - 无Cookies!
  })
解决方案1:使用createIsomorphicFn(尽可能在客户端运行)
typescript
import { createIsomorphicFn } from '@tanstack/react-start/server'

const getData = createIsomorphicFn()
  .handler(async () => {
    // 尽可能在客户端运行,保留完整浏览器上下文
    const response = await fetch('https://api.example.com/user')
    return response.json()
  })
解决方案2:手动转发请求头
typescript
import { createServerFn } from '@tanstack/react-start/server'
import { getRequestHeaders } from '@tanstack/react-start/server'

const getData = createServerFn()
  .handler(async () => {
    const headers = getRequestHeaders() // 获取浏览器的原始请求头

    const response = await fetch('https://api.example.com/user', {
      headers: {
        'Cookie': headers.get('cookie') || '',
        'X-XSRF-TOKEN': headers.get('x-xsrf-token') || '',
        'Origin': headers.get('origin') || '',
      }
    })

    return response.json()
  })
使用场景
  • createIsomorphicFn
    :最适合读取操作,保持完整浏览器上下文
  • 手动转发:必须在服务器端运行的操作(访问密钥、数据库)必填

Better Auth Integration

Better Auth集成优化

Issue: Better Auth cookie caching has edge cases with TanStack Start:
  1. Session cookie not re-set after expiry
  2. Session token cookie issues with certain plugins (
    multiSession
    ,
    lastLoginMethod
    ,
    oneTap
    )
  3. Hard reload/direct URL doesn't read cookies (works with client navigation only)
Solution: Use Better Auth's official TanStack Start plugin
typescript
import { betterAuth } from 'better-auth'
import { reactStartCookies } from 'better-auth/plugins'

export const auth = betterAuth({
  plugins: [
    reactStartCookies(), // Handles cookie setting for TanStack Start
  ],
  // ... other config
})
Known Limitations:
  • Some edge cases remain with hard reloads
  • Session cookie re-setting after expiry may not work consistently

问题:Better Auth的Cookie缓存与TanStack Start存在边缘情况:
  1. 会话Cookie过期后未重新设置
  2. 某些插件(
    multiSession
    ,
    lastLoginMethod
    ,
    oneTap
    )存在会话令牌Cookie问题
  3. 硬刷新/直接URL无法读取Cookie(仅客户端导航有效)
解决方案:使用Better Auth官方的TanStack Start插件
typescript
import { betterAuth } from 'better-auth'
import { reactStartCookies } from 'better-auth/plugins'

export const auth = betterAuth({
  plugins: [
    reactStartCookies(), // 处理TanStack Start的Cookie设置
  ],
  // ... 其他配置
})
已知限制
  • 硬刷新仍存在一些边缘情况
  • 会话Cookie过期后重新设置可能不一致

Database Integration

数据库集成

Prisma with Cloudflare Workers

Prisma与Cloudflare Workers

Issue: Deploying with Prisma Edge fails with "No such module 'assets/.prisma/client/edge'" error.
Solution: Configure Prisma for Cloudflare runtime
prisma
// prisma/schema.prisma
generator client {
  provider   = "prisma-client"
  output     = "../src/generated/prisma"
  engineType = "library"
  runtime    = "cloudflare" // or "workerd"
}
Alternative Configuration:
prisma
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
Then use with Cloudflare Hyperdrive:
typescript
import { PrismaClient } from '@prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import { Pool } from 'pg'

export const getUser = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env

    const pool = new Pool({ connectionString: env.HYPERDRIVE.connectionString })
    const adapter = new PrismaPg(pool)
    const prisma = new PrismaClient({ adapter })

    return prisma.user.findMany()
  })
问题:使用Prisma Edge部署时出现"No such module 'assets/.prisma/client/edge'"错误。
解决方案:为Cloudflare运行时配置Prisma
prisma
// prisma/schema.prisma
generator client {
  provider   = "prisma-client"
  output     = "../src/generated/prisma"
  engineType = "library"
  runtime    = "cloudflare" // 或 "workerd"
}
替代配置
prisma
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
然后与Cloudflare Hyperdrive配合使用
typescript
import { PrismaClient } from '@prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import { Pool } from 'pg'

export const getUser = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env

    const pool = new Pool({ connectionString: env.HYPERDRIVE.connectionString })
    const adapter = new PrismaPg(pool)
    const prisma = new PrismaClient({ adapter })

    return prisma.user.findMany()
  })

D1 Database

D1数据库

typescript
export const getUsers = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env
    const result = await env.DB.prepare('SELECT * FROM users').all()
    return result.results
  })
Use with
drizzle-orm-d1
skill for type-safe ORM.

typescript
export const getUsers = createServerFn()
  .handler(async ({ request }) => {
    const env = request.context.cloudflare.env
    const result = await env.DB.prepare('SELECT * FROM users').all()
    return result.results
  })
如需类型安全ORM,可配合
drizzle-orm-d1
技能使用。

Known Issues Prevention

已知问题预防

This skill prevents 9 documented issues:
本技能指南可预防9种已记录的问题:

Issue #1: Middleware Does Not Catch Server Function Errors

问题1:中间件无法捕获服务器函数错误

Error: Errors thrown by server functions bypass middleware try-catch blocks Source: GitHub Issue #6381 Status: Fixed in v1.155+ (expected release)
Why It Happens: Server function errors are returned as error objects in the response, not thrown directly.
Prevention (workaround for v1.154 and earlier):
typescript
import { createMiddleware } from '@tanstack/react-start/server'

const middleware = createMiddleware().server(async (ctx) => {
  try {
    const r = await ctx.next()

    // Check for error in response object
    if ('error' in r && r.error) {
      throw r.error
    }

    return r
  } catch (error: any) {
    console.error("Middleware caught an error:", error)
    return new Response("An error occurred", { status: 500 })
  }
})
错误:服务器函数抛出的错误绕过中间件的try-catch块 来源GitHub问题 #6381 状态:v1.155+版本已修复(即将发布)
原因:服务器函数错误以错误对象形式返回响应,而非直接抛出。
预防措施(v1.154及更早版本的解决方法):
typescript
import { createMiddleware } from '@tanstack/react-start/server'

const middleware = createMiddleware().server(async (ctx) => {
  try {
    const r = await ctx.next()

    // 检查响应对象中的错误
    if ('error' in r && r.error) {
      throw r.error
    }

    return r
  } catch (error: any) {
    console.error("中间件捕获到错误:", error)
    return new Response("发生错误", { status: 500 })
  }
})

Issue #2: File Upload Streaming Not Supported

问题2:不支持文件上传流式处理

Error: Large file uploads consume excessive memory Source: GitHub Issue #5704 Status: Open, no fix planned
Why It Happens: Framework automatically calls
await request.formData()
before handler runs, loading entire file into memory.
Prevention:
  1. Implement client-side file size validation
  2. Use Cloudflare R2 multipart upload API directly for large files
  3. Set reasonable file size limits in upload UI
See File Upload Limitation section for details.
错误:大文件上传消耗过多内存 来源GitHub问题 #5704 状态:开放,暂无修复计划
原因:框架在处理程序运行前自动调用
await request.formData()
,将整个文件加载到内存。
预防措施
  1. 实现客户端文件大小验证
  2. 大文件直接使用Cloudflare R2分块上传API
  3. 在上传UI中设置合理的文件大小限制
详情请查看文件上传限制章节。

Issue #3: Server Function Redirects Return Undefined

问题3:服务器函数重定向返回Undefined

Error: Type errors when using server function result after redirect Source: GitHub PR #6295 Status: Open PR
Why It Happens: Redirects return void, but return type doesn't reflect this.
Prevention: Always check server function return value before use
typescript
const result = await login({ username, password })

if (result) {
  // Safe to use result
  console.log(result.name)
}
错误:重定向后使用服务器函数结果时出现类型错误 来源GitHub PR #6295 状态:开放PR
原因:重定向返回void,但返回类型未反映此情况。
预防措施:使用服务器函数返回值前务必检查
typescript
const result = await login({ username, password })

if (result) {
  // 安全使用result
  console.log(result.name)
}

Issue #4: Stateful Auth Cookies Not Forwarded

问题4:有状态认证Cookies未转发

Error: 401 Unauthorized when calling stateful backend APIs from server functions Source: GitHub Discussion #6289
Why It Happens: Server functions originate from Start server, not browser, so cookies aren't forwarded.
Prevention: Use
createIsomorphicFn
or manual header forwarding
错误:从服务器函数调用有状态后端API时出现401未授权 来源GitHub讨论 #6289
原因:服务器函数请求来自Start服务器而非浏览器,因此Cookies未被转发。
预防措施:使用
createIsomorphicFn
或手动转发请求头
详情请查看有状态后端集成章节。

Issue #5: Prisma Edge Module Not Found

问题5:Prisma Edge模块未找到

Error: "No such module 'assets/.prisma/client/edge'" Source: Cloudflare Workers SDK Issue #10969 Status: Resolved with runtime config
Why It Happens: Prisma Edge client not properly bundled for Workers environment.
Prevention: Configure Prisma with
runtime = "cloudflare"
in schema.prisma
错误:"No such module 'assets/.prisma/client/edge'" 来源Cloudflare Workers SDK问题 #10969 状态:通过运行时配置已解决
原因:Prisma Edge客户端未针对Workers环境正确打包。
预防措施:在schema.prisma中配置
runtime = "cloudflare"
详情请查看Prisma与Cloudflare Workers章节。

Issue #6: Better Auth Cookie Caching Issues

问题6:Better Auth Cookie缓存问题

Error: Session cookies not set/refreshed properly Source: Better Auth Issues #4389, #5639
Why It Happens: Better Auth's default cookie handling doesn't account for Start's execution model.
Prevention: Use
reactStartCookies()
plugin
错误:会话Cookie未正确设置/刷新 来源Better Auth问题 #4389、#5639
原因:Better Auth的默认Cookie处理未考虑Start的执行模型。
预防措施:使用
reactStartCookies()
插件
详情请查看Better Auth集成优化章节。

Issue #7: Missing nodejs_compat Flag

问题7:缺少nodejs_compat标志

Error: Runtime errors when using Node.js APIs on Cloudflare Workers Source: Cloudflare Workers Guide
Why It Happens: TanStack Start uses Node.js APIs that require compatibility flag.
Prevention: Add
compatibility_flags = ["nodejs_compat"]
to wrangler.toml
错误:在Cloudflare Workers上使用Node.js API时出现运行时错误 来源Cloudflare Workers指南
原因:TanStack Start使用需要兼容标志的Node.js API。
预防措施:在wrangler.toml中添加
compatibility_flags = ["nodejs_compat"]

Issue #8: Prerendering Fails with Cloudflare Bindings

问题8:使用Cloudflare绑定时预渲染失败

Error: Build fails when routes with loaders use D1/KV/R2 Source: Cloudflare Workers Guide
Why It Happens: Prerendering runs at build time without access to Cloudflare bindings.
Prevention: Disable prerendering for routes with bindings, or use conditional logic
错误:当路由的loaders使用D1/KV/R2时构建失败 来源Cloudflare Workers指南
原因:预渲染在构建时运行,无法访问Cloudflare绑定。
预防措施:对使用绑定的路由禁用预渲染,或使用条件逻辑
详情请查看预渲染注意事项章节。

Issue #9: Vinxi Migration Errors

问题9:Vinxi迁移错误

Error: "invariant failed: could not find the nearest match" after upgrading to v1.121.0+ Source: Release v1.121.0
Why It Happens: v1.121.0 migrated from Vinxi to Vite with breaking changes.
Prevention: Follow complete migration guide

错误:升级到v1.121.0+后出现"invariant failed: could not find the nearest match" 来源v1.121.0版本发布
原因:v1.121.0从Vinxi迁移到Vite,包含破坏性变更。
预防措施:遵循完整的迁移指南
详情请查看从Vinxi迁移到Vite章节。

Performance Optimization

性能优化

Static Process.env Replacement

静态process.env替换

Feature: Build-time replacement of
process.env.NODE_ENV
for better optimization (v1.154.0+)
typescript
// This condition is statically evaluated and dead code eliminated
if (process.env.NODE_ENV === 'production') {
  // Production-only code
} else {
  // Development-only code (removed in prod build)
}
Automatic: No configuration needed, works out of the box.
特性:构建时替换
process.env.NODE_ENV
以优化性能(v1.154.0+)
typescript
// 此条件会被静态评估,无用代码会被移除
if (process.env.NODE_ENV === 'production') {
  // 仅生产环境代码
} else {
  // 仅开发环境代码(生产构建中会被移除)
}
自动生效:无需配置,开箱即用。

Development Performance with Many Routes

多路由场景下的开发性能

Issue: Apps with 100+ routes generate 700+ HTTP requests in Vite dev mode.
Why:
routeTree.gen.ts
statically imports every route for type generation, even though
autoCodeSplitting
is enabled by default.
Impact: Slow dev server, hits proxy rate limits (ngrok 360 req/min)
Status: Expected behavior until Router v2. Not a bug, architectural limitation.
Workarounds:
  • Use production builds for testing with many routes
  • Reduce route count during development
  • Use local tunneling without rate limits (Cloudflare Tunnel instead of ngrok)

问题:拥有100+路由的应用在Vite开发模式下会生成700+ HTTP请求。
原因
routeTree.gen.ts
静态导入每个路由用于类型生成,即使默认启用了
autoCodeSplitting
影响:开发服务器缓慢,触发代理速率限制(ngrok限制360请求/分钟)
状态:Router v2前的预期行为,并非bug,属于架构限制。
解决方法
  • 测试多路由场景时使用生产构建
  • 开发期间减少路由数量
  • 使用无速率限制的本地隧道(如Cloudflare Tunnel替代ngrok)

Additional Resources

更多资源

Official Documentation:
Migration Guides:
Related Skills:
  • cloudflare-worker-base
    - Cloudflare Workers deployment patterns
  • drizzle-orm-d1
    - Type-safe D1 database access
  • ai-sdk-core
    - AI integration with server functions
  • react-hook-form-zod
    - Form handling with validation

Last verified: 2026-01-21 | Skill version: 2.0.0 | Changes: Expanded from draft with 9 documented issues, migration guide, Cloudflare deployment, auth patterns, and database integration
官方文档
迁移指南
相关技能
  • cloudflare-worker-base
    - Cloudflare Workers部署模式
  • drizzle-orm-d1
    - 类型安全D1数据库访问
  • ai-sdk-core
    - 服务器函数的AI集成
  • react-hook-form-zod
    - 带验证的表单处理

最后验证时间:2026-01-21 | 技能版本:2.0.0 | 更新内容:从草稿扩展为包含9种已记录问题、迁移指南、Cloudflare部署、认证模式和数据库集成的完整指南