tanstack-start
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTanStack 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: (Jan 21, 2026)
@tanstack/react-start@1.154.0Production 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支持。
当前包版本: (2026年1月21日)
@tanstack/react-start@1.154.0生产环境就绪状态:
- ✅ 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
undefinedbash
undefinedCreate 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
undefinednpm run build
wrangler deploy
undefinedDependencies
依赖配置
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
破坏性变更
| Change | Old (Vinxi) | New (Vite) |
|---|---|---|
| Package name | | |
| Config file | | |
| API routes | | |
| Entry files | | |
| Source folder | | |
| Dev command | | |
| 变更项 | 旧版(Vinxi) | 新版(Vite) |
|---|---|---|
| 包名称 | | |
| 配置文件 | | |
| API路由 | | |
| 入口文件 | | |
| 源码目录 | | |
| 开发命令 | | |
Migration Steps
迁移步骤
bash
undefinedbash
undefined1. 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/
undefinedmv app/ src/
undefinedCreate 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 , , reinstall
node_modules/package-lock.jsonIssue: Auto-generated files duplicating
Cause: Old Vinxi config interfering
Fix: Delete all files, restart dev server
app.config.timestamp_*app.config.*Reference: Official Migration Guide | LogRocket Migration Article
错误:"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问题:自动生成的文件重复
原因:旧版Vinxi配置干扰
修复:删除所有文件,重启开发服务器
app.config.timestamp_*app.config.*参考:官方迁移指南 | LogRocket迁移文章
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"] # REQUIREDtoml
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
undefinedmain = "@tanstack/react-start/server-entry"
[observability]
enabled = true # 可选:启用监控
undefinedvite.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
undefinedtoml
undefinedD1 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 that query D1/KV/R2, prerendering will fail because bindings aren't available at build time.
loadersSolutions:
- Disable prerendering for routes with bindings:
typescript
export const Route = createFileRoute('/users')({
loader: async () => {
// This route queries D1
},
// Disable prerendering
prerender: false
})- Use remote bindings during builds (requires running):
wrangler dev
bash
undefined关键提示:预渲染在构建阶段使用本地环境变量运行,而非Cloudflare绑定。
问题:如果路由的查询D1/KV/R2,预渲染会失败,因为构建时无法访问绑定。
loaders解决方案:
- 对使用绑定的路由禁用预渲染:
typescript
export const Route = createFileRoute('/users')({
loader: async () => {
// 该路由查询D1
},
// 禁用预渲染
prerender: false
})- 构建时使用远程绑定(需要运行):
wrangler dev
bash
undefinedIn 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+
Reference: Cloudflare Workers Guide
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 for multipart/form-data requests, loading entire files into memory BEFORE the handler runs.
await request.formData()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:
- 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>
}- 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("文件过大")
}
})解决方法:
- 客户端验证(不绝对安全,可被绕过):
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>
}- 直接使用Cloudflare R2分块上传API处理大文件(绕过Start的表单处理)。
状态:开放问题 #5704,目前暂无修复计划。
Server Function Redirects Return Undefined
服务器函数重定向返回Undefined
When a server function performs a redirect, the promise resolves to instead of the declared return type.
undefinedtypescript
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会解析为而非声明的返回类型。
undefinedtypescript
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:
- : Best for read operations, maintains full browser context
createIsomorphicFn - Manual forwarding: Required for operations that must run server-side (secrets, DB access)
Reference: GitHub Discussion #6289
问题:使用有状态后端时,服务器函数会丢失认证上下文,因为请求来自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:
- Session cookie not re-set after expiry
- Session token cookie issues with certain plugins (,
multiSession,lastLoginMethod)oneTap - 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
References: Issue #4389, Issue #5639
问题:Better Auth的Cookie缓存与TanStack Start存在边缘情况:
- 会话Cookie过期后未重新设置
- 某些插件(,
multiSession,lastLoginMethod)存在会话令牌Cookie问题oneTap - 硬刷新/直接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()
})Reference: Cloudflare Workers SDK Issue #10969
问题:使用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 skill for type-safe ORM.
drizzle-orm-d1typescript
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-d1Known 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 before handler runs, loading entire file into memory.
await request.formData()Prevention:
- Implement client-side file size validation
- Use Cloudflare R2 multipart upload API directly for large files
- Set reasonable file size limits in upload UI
See File Upload Limitation section for details.
错误:大文件上传消耗过多内存
来源:GitHub问题 #5704
状态:开放,暂无修复计划
原因:框架在处理程序运行前自动调用,将整个文件加载到内存。
await request.formData()预防措施:
- 实现客户端文件大小验证
- 大文件直接使用Cloudflare R2分块上传API
- 在上传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 or manual header forwarding
createIsomorphicFnSee Stateful Backend Integration section.
错误:从服务器函数调用有状态后端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 in schema.prisma
runtime = "cloudflare"See Prisma with Cloudflare Workers section.
错误:"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 plugin
reactStartCookies()See Better Auth Integration section.
错误:会话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 to wrangler.toml
compatibility_flags = ["nodejs_compat"]错误:在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
See Prerendering Gotchas section.
错误:当路由的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
See Migration from Vinxi to Vite section.
错误:升级到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 for better optimization (v1.154.0+)
process.env.NODE_ENVtypescript
// 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.
特性:构建时替换以优化性能(v1.154.0+)
process.env.NODE_ENVtypescript
// 此条件会被静态评估,无用代码会被移除
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: statically imports every route for type generation, even though is enabled by default.
routeTree.gen.tsautoCodeSplittingImpact: 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)
Reference: GitHub Discussion #6353
问题:拥有100+路由的应用在Vite开发模式下会生成700+ HTTP请求。
原因:静态导入每个路由用于类型生成,即使默认启用了。
routeTree.gen.tsautoCodeSplitting影响:开发服务器缓慢,触发代理速率限制(ngrok限制360请求/分钟)
状态:Router v2前的预期行为,并非bug,属于架构限制。
解决方法:
- 测试多路由场景时使用生产构建
- 开发期间减少路由数量
- 使用无速率限制的本地隧道(如Cloudflare Tunnel替代ngrok)
Additional Resources
更多资源
Official Documentation:
Migration Guides:
Related Skills:
- - Cloudflare Workers deployment patterns
cloudflare-worker-base - - Type-safe D1 database access
drizzle-orm-d1 - - AI integration with server functions
ai-sdk-core - - Form handling with validation
react-hook-form-zod
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 Workers部署模式
cloudflare-worker-base - - 类型安全D1数据库访问
drizzle-orm-d1 - - 服务器函数的AI集成
ai-sdk-core - - 带验证的表单处理
react-hook-form-zod
最后验证时间:2026-01-21 | 技能版本:2.0.0 | 更新内容:从草稿扩展为包含9种已记录问题、迁移指南、Cloudflare部署、认证模式和数据库集成的完整指南