前言
2026 年,前端全栈开发已经进入了一个新阶段。TanStack 生态(Router + Query + Start)与 Cloudflare 边缘基础设施的结合,正在重新定义"现代全栈应用"的标准。本文将带你从 0 到 1,实战构建一个基于 TanStack Start + Cloudflare Workers + D1 + KV + R2 的全栈应用,涵盖类型安全路由、Server Functions、边缘数据库、对象存储等核心能力。
一、技术选型与架构概览
1.1 为什么选择 TanStack + Cloudflare?
| 维度 | TanStack Start | Cloudflare |
|---|---|---|
| 路由 | 全类型安全,零运行时开销 | - |
| 数据获取 | TanStack Query 内置,乐观更新 | - |
| 部署 | 多平台支持,Cloudflare 官方合作 | 全球 300+ 边缘节点 |
| 数据库 | Server Functions 直接访问 D1 | 边缘 SQLite,延迟 < 10ms |
| 缓存 | TanStack Query 智能缓存 | KV 全局低延迟读取 |
| 存储 | Server Functions 直接操作 R2 | S3 兼容对象存储 |
| 成本 | 开源免费 | 免费额度:10万请求/天 |
核心优势:
- 类型安全贯穿始终:从路由参数到 API 响应,TypeScript 类型自动推导
- Server Functions 简化心智模型:告别 Next.js 的 RSC 复杂性,直接写服务端逻辑
- 边缘原生:HTML 在距离用户最近的节点生成,首屏延迟从 200ms 降至 20ms
- 零 Vendor Lock-in:TanStack Start 支持 Vercel、Netlify、Railway、Node.js 等任意平台
1.2 架构图
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ TanStack │ │ TanStack │ │ React UI │ │
│ │ Router │ │ Query │ │ (shadcn/ui) │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└────────────────────┬────────────────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────────────────┐
│ Cloudflare Edge Network │
│ ┌─────────────────────────────────────────────────┐ │
│ │ TanStack Start (SSR) │ │
│ │ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Server │ │ Server Functions │ │ │
│ │ │ Entry │ │ (createServerFn) │ │ │
│ │ └─────────────┘ └─────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ D1 │ │ KV │ │ R2 │ │ DO │ │
│ │ SQLite │ │ 缓存 │ │ 对象存储│ │ 状态 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────┘
二、环境准备与项目初始化
2.1 前置要求
- Node.js 22 LTS+
- npm 10+ 或 pnpm 9+
- Cloudflare 账号(免费版即可)
- Wrangler CLI:
npm install -g wrangler
2.2 创建项目(官方推荐方式)
Cloudflare 官方提供了一键创建模板,自动配置好所有集成:
# 使用 Cloudflare 官方模板创建
npm create cloudflare@latest -- my-tanstack-app --framework=tanstack-start
# 进入项目
cd my-tanstack-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
创建完成后,项目结构如下:
my-tanstack-app/
├── app/
│ ├── routes/ # 文件系统路由
│ │ ├── __root.tsx # 根布局
│ │ ├── index.tsx # 首页
│ │ └── about.tsx # 关于页
│ ├── components/ # 共享组件
│ ├── utils/ # 工具函数
│ ├── server/ # 服务端逻辑
│ │ ├── db.ts # 数据库连接
│ │ └── functions.ts # Server Functions
│ └── client.tsx # 客户端入口
├── public/ # 静态资源
├── vite.config.ts # Vite 配置(含 Cloudflare 插件)
├── wrangler.jsonc # Cloudflare 配置
└── package.json
2.3 关键配置文件解析
vite.config.ts — 这是整个集成的核心:
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
// ⚠️ 顺序很重要:cloudflare 必须在 tanstackStart 之前
cloudflare({
viteEnvironment: { name: 'ssr' } // 指定 SSR 环境
}),
tanstackStart(),
react(),
],
})
wrangler.jsonc — Cloudflare 部署配置:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-tanstack-app",
"compatibility_date": "2026-06-03",
"compatibility_flags": ["nodejs_compat"], // 必须:启用 Node.js 兼容模式
"main": "@tanstack/react-start/server-entry", // 服务端入口
"observability": {
"enabled": true // 启用监控
},
// 绑定 D1 数据库
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "your-database-id"
}
],
// 绑定 KV 命名空间
"kv_namespaces": [
{
"binding": "KV",
"id": "your-kv-id"
}
],
// 绑定 R2 存储桶
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "my-bucket"
}
]
}
package.json scripts:
{
"scripts": {
"dev": "vite dev",
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"deploy": "npm run build && wrangler deploy",
"cf-typegen": "wrangler types"
}
}
三、类型安全路由:TanStack Router
3.1 文件系统路由
TanStack Router 采用文件系统路由,每个文件自动成为一个路由,且完全类型安全。
// app/routes/posts.$postId.tsx
// 路由路径: /posts/:postId
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
// 参数自动类型推导:postId 为 string
component: PostDetail,
// 加载数据(在服务端或客户端执行)
loader: async ({ params }) => {
// params.postId 是 string 类型,无需手动断言
const post = await getPost({ data: { id: params.postId } })
return { post }
},
})
function PostDetail() {
const { post } = Route.useLoaderData() // post 类型自动推导
const { postId } = Route.useParams() // postId 类型为 string
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
)
}
3.2 搜索参数类型安全
这是 TanStack Router 的杀手级特性,处理复杂的过滤、分页、排序状态:
// app/routes/products.tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
// 定义搜索参数 schema
const productSearchSchema = z.object({
page: z.number().catch(1),
category: z.string().optional(),
sort: z.enum(['price', 'name', 'date']).catch('date'),
order: z.enum(['asc', 'desc']).catch('desc'),
})
export const Route = createFileRoute('/products')({
validateSearch: productSearchSchema,
component: ProductsPage,
})
function ProductsPage() {
const { page, category, sort, order } = Route.useSearch()
// 所有参数都是类型安全的,且自动验证
const navigate = Route.useNavigate()
// 更新 URL 参数,同时更新组件状态
const handleSort = (newSort: string) => {
navigate({ search: { sort: newSort, page: 1 } })
}
return (
<div>
<SortButton
active={sort === 'price'}
onClick={() => handleSort('price')}
/>
<ProductList page={page} category={category} />
</div>
)
}
3.3 路由预加载
TanStack Router 支持智能预加载,鼠标悬停时即可预取数据和组件:
<<Link
to="/posts/$postId"
params={{ postId: post.id }}
preload="intent" // 悬停时预加载
preloadDelay={100} // 延迟 100ms,避免快速划过
>
{post.title}
</Link>
四、Server Functions:服务端逻辑简化
4.1 什么是 Server Functions?
Server Functions 是 TanStack Start 的核心创新。它允许你编写仅在服务端运行的函数,但像调用普通函数一样从客户端调用,自动处理序列化、HTTP 请求、类型安全。
// app/server/functions.ts
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
// 定义输入验证 schema
const createPostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
tags: z.array(z.string()).optional(),
})
// 创建 Server Function
export const createPost = createServerFn({ method: 'POST' })
.inputValidator(createPostSchema)
.handler(async ({ data }) => {
// 这里完全在服务端运行
// 可以直接访问数据库、环境变量、文件系统等
const { title, content, tags } = data
// 假设使用 Drizzle ORM + D1
const result = await db.insert(posts).values({
title,
content,
tags: tags?.join(',') ?? '',
createdAt: new Date(),
}).returning()
return result[0]
})
在客户端调用:
// app/components/PostForm.tsx
import { createPost } from '~/server/functions'
import { useMutation } from '@tanstack/react-query'
function PostForm() {
const mutation = useMutation({
mutationFn: createPost,
onSuccess: (data) => {
// data 类型自动推导为 Post 类型
toast.success(`文章创建成功: ${data.title}`)
navigate({ to: '/posts/$postId', params: { postId: data.id } })
},
})
const handleSubmit = (formData: CreatePostInput) => {
mutation.mutate({ data: formData })
}
return <form onSubmit={handleSubmit}>...</form>
}
4.2 访问 Cloudflare Bindings
在 Server Functions 中直接访问 D1、KV、R2:
import { createServerFn } from '@tanstack/react-start'
// 获取 D1 数据
export const getPosts = createServerFn({ method: 'GET' })
.handler(async ({ request }) => {
// 通过 request.context.cloudflare.env 访问 bindings
const env = request.context.cloudflare.env
const result = await env.DB.prepare(
'SELECT * FROM posts ORDER BY created_at DESC LIMIT 20'
).all()
return result.results
})
// KV 缓存读取
export const getConfig = createServerFn({ method: 'GET' })
.handler(async ({ request }) => {
const env = request.context.cloudflare.env
// 先查 KV 缓存
const cached = await env.KV.get('app:config')
if (cached) return JSON.parse(cached)
// 缓存未命中,查 D1
const config = await env.DB.prepare('SELECT * FROM config').first()
// 写入 KV,TTL 1 小时
await env.KV.put('app:config', JSON.stringify(config), { expirationTtl: 3600 })
return config
})
// R2 文件上传
export const uploadImage = createServerFn({ method: 'POST' })
.handler(async ({ request }) => {
const env = request.context.cloudflare.env
const formData = await request.formData()
const file = formData.get('file') as File
const key = `uploads/${crypto.randomUUID()}-${file.name}`
await env.BUCKET.put(key, file.stream(), {
httpMetadata: { contentType: file.type },
})
return { url: `https://cdn.example.com/${key}` }
})
4.3 中间件与认证
Server Functions 支持中间件链,实现认证、日志、错误处理等横切关注点:
import { createMiddleware, createServerFn } from '@tanstack/react-start'
// 认证中间件
const authMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
// 客户端:添加认证头
const token = localStorage.getItem('token')
return next({
headers: { Authorization: `Bearer ${token}` },
})
})
.server(async ({ next }) => {
// 服务端:验证 token
const headers = getRequestHeaders()
const token = headers.get('Authorization')?.replace('Bearer ', '')
if (!token) throw new Error('Unauthorized')
const user = await verifyToken(token)
return next({ context: { user } }) // 将 user 注入上下文
})
// 使用中间件
export const getMyPosts = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context }) => {
// context.user 自动类型推导
const user = context.user
return db.query.posts.findMany({
where: eq(posts.authorId, user.id),
})
})
五、数据管理:TanStack Query 深度集成
5.1 与 Server Functions 结合
TanStack Start 内置 TanStack Query,无需额外配置:
// app/routes/posts.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useSuspenseQuery } from '@tanstack/react-query'
import { getPosts } from '~/server/functions'
export const Route = createFileRoute('/posts')({
component: PostsPage,
loader: async () => {
// 在服务端预取数据,支持 SSR
const posts = await getPosts()
return { posts }
},
})
function PostsPage() {
// 使用 Suspense Query,数据已在服务端预取
const { data: posts } = useSuspenseQuery({
queryKey: ['posts'],
queryFn: () => getPosts(),
})
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
)
}
5.2 乐观更新
TanStack Query 的乐观更新与 Server Functions 无缝配合:
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { createPost } from '~/server/functions'
function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: createPost,
// 乐观更新
onMutate: async (newPost) => {
// 取消正在进行的重新获取
await queryClient.cancelQueries({ queryKey: ['posts'] })
// 保存之前的值
const previousPosts = queryClient.getQueryData(['posts'])
// 乐观更新缓存
queryClient.setQueryData(['posts'], (old) => [
{ ...newPost.data, id: 'temp-id', createdAt: new Date() },
...(old ?? []),
])
return { previousPosts }
},
// 错误时回滚
onError: (err, newPost, context) => {
queryClient.setQueryData(['posts'], context?.previousPosts)
},
// 完成后重新获取确保数据一致
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
}
5.3 无限滚动与分页
import { useInfiniteQuery } from '@tanstack/react-query'
import { getPosts } from '~/server/functions'
function useInfinitePosts() {
return useInfiniteQuery({
queryKey: ['posts', 'infinite'],
queryFn: async ({ pageParam = 1 }) => {
return getPosts({ data: { page: pageParam, limit: 20 } })
},
getNextPageParam: (lastPage, allPages) => {
return lastPage.length === 20 ? allPages.length + 1 : undefined
},
initialPageParam: 1,
})
}
六、数据库实战:D1 + Drizzle ORM
6.1 配置 Drizzle ORM
D1 是 Cloudflare 的边缘 SQLite 数据库,与 Drizzle ORM 配合极佳:
// app/server/db.ts
import { drizzle } from 'drizzle-orm/d1'
import * as schema from './schema'
// 在 Server Functions 中初始化
export function getDb(env: Env) {
return drizzle(env.DB, { schema })
}
// app/server/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
content: text('content').notNull(),
slug: text('slug').notNull().unique(),
authorId: integer('author_id').notNull(),
tags: text('tags'), // 逗号分隔
published: integer('published', { mode: 'boolean' }).default(false),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
updatedAt: integer('updated_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
})
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
name: text('name').notNull(),
avatar: text('avatar'),
role: text('role').default('user'), // admin, user
})
6.2 数据库操作封装
// app/server/posts.server.ts
import { eq, desc, like, sql } from 'drizzle-orm'
import { posts, users } from './schema'
export async function getPostList(db: D1Database, options: {
page?: number
limit?: number
category?: string
search?: string
}) {
const { page = 1, limit = 20, category, search } = options
const offset = (page - 1) * limit
let query = db.select().from(posts)
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(limit)
.offset(offset)
if (search) {
query = query.where(like(posts.title, `%${search}%`))
}
return query.all()
}
export async function getPostBySlug(db: D1Database, slug: string) {
const result = await db.select()
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.slug, slug))
.get()
return result
}
6.3 在 Server Functions 中使用
// app/server/functions.ts
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { getDb } from './db'
import { getPostList, getPostBySlug } from './posts.server'
export const getPosts = createServerFn({ method: 'GET' })
.inputValidator(z.object({
page: z.number().optional(),
search: z.string().optional(),
}).optional())
.handler(async ({ data, request }) => {
const env = request.context.cloudflare.env
const db = getDb(env)
return getPostList(db, data ?? {})
})
export const getPost = createServerFn({ method: 'GET' })
.inputValidator(z.object({ slug: z.string() }))
.handler(async ({ data, request }) => {
const env = request.context.cloudflare.env
const db = getDb(env)
const post = await getPostBySlug(db, data.slug)
if (!post) throw new Error('Post not found')
return post
})
七、KV 缓存策略
7.1 缓存模式设计
// app/server/cache.ts
import { createServerFn } from '@tanstack/react-start'
// 缓存键命名规范
const CACHE_KEYS = {
post: (slug: string) => `post:${slug}`,
postList: (page: number, search?: string) =>
`posts:page:${page}:search:${search ?? 'all'}`,
config: () => 'app:config',
user: (id: number) => `user:${id}`,
}
// 带缓存的数据获取
export const getPostWithCache = createServerFn({ method: 'GET' })
.inputValidator(z.object({ slug: z.string() }))
.handler(async ({ data, request }) => {
const env = request.context.cloudflare.env
const cacheKey = CACHE_KEYS.post(data.slug)
// 1. 查 KV 缓存
const cached = await env.KV.get(cacheKey)
if (cached) {
return JSON.parse(cached)
}
// 2. 缓存未命中,查 D1
const db = getDb(env)
const post = await getPostBySlug(db, data.slug)
// 3. 写入 KV,设置 TTL
await env.KV.put(cacheKey, JSON.stringify(post), {
expirationTtl: 3600, // 1 小时
})
return post
})
// 缓存失效(数据更新时调用)
export const invalidatePostCache = createServerFn({ method: 'POST' })
.inputValidator(z.object({ slug: z.string() }))
.handler(async ({ data, request }) => {
const env = request.context.cloudflare.env
// 删除特定文章缓存
await env.KV.delete(CACHE_KEYS.post(data.slug))
// 删除列表缓存(使用前缀匹配或存储列表键集合)
// 注意:KV 不支持前缀删除,需要维护一个索引键
await env.KV.delete('posts:index')
})
7.2 缓存预热
// 在构建时或定时任务中预热缓存
export const warmupCache = createServerFn({ method: 'POST' })
.handler(async ({ request }) => {
const env = request.context.cloudflare.env
const db = getDb(env)
// 获取热门文章
const hotPosts = await db.select()
.from(posts)
.where(eq(posts.published, true))
.orderBy(desc(posts.views))
.limit(10)
.all()
// 并行写入 KV
await Promise.all(
hotPosts.map(post =>
env.KV.put(
CACHE_KEYS.post(post.slug),
JSON.stringify(post),
{ expirationTtl: 7200 }
)
)
)
return { warmed: hotPosts.length }
})
八、R2 对象存储实战
8.1 文件上传与读取
// app/server/storage.ts
import { createServerFn } from '@tanstack/react-start'
// 上传文件
export const uploadFile = createServerFn({ method: 'POST' })
.handler(async ({ request }) => {
const env = request.context.cloudflare.env
const formData = await request.formData()
const file = formData.get('file') as File
if (!file) throw new Error('No file provided')
// 生成唯一文件名
const ext = file.name.split('.').pop()
const key = `uploads/${Date.now()}-${crypto.randomUUID()}.${ext}`
// 上传至 R2
await env.BUCKET.put(key, file.stream(), {
httpMetadata: {
contentType: file.type,
contentDisposition: `inline; filename="${file.name}"`,
},
customMetadata: {
originalName: file.name,
uploadedAt: new Date().toISOString(),
},
})
// 生成公开访问 URL(需配置 R2 自定义域)
const publicUrl = `https://cdn.yourdomain.com/${key}`
return { key, url: publicUrl, size: file.size }
})
// 获取文件(用于私有文件)
export const getSignedUrl = createServerFn({ method: 'GET' })
.inputValidator(z.object({ key: z.string() }))
.handler(async ({ data, request }) => {
const env = request.context.cloudflare.env
const object = await env.BUCKET.get(data.key)
if (!object) throw new Error('File not found')
// 返回文件流
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
'Content-Length': object.size.toString(),
},
})
})
8.2 图片处理优化
结合 Cloudflare Images(或 R2 + 图片转换):
// 生成不同尺寸的图片 URL
export function getImageUrl(key: string, options?: {
width?: number
height?: number
quality?: number
format?: 'webp' | 'avif' | 'jpeg'
}) {
const baseUrl = `https://cdn.yourdomain.com/${key}`
if (!options) return baseUrl
const params = new URLSearchParams()
if (options.width) params.set('w', options.width.toString())
if (options.height) params.set('h', options.height.toString())
if (options.quality) params.set('q', options.quality.toString())
if (options.format) params.set('f', options.format)
return `${baseUrl}?${params.toString()}`
}
// 在组件中使用
<img
src={getImageUrl(post.cover, { width: 800, format: 'webp' })}
srcSet={`
${getImageUrl(post.cover, { width: 400, format: 'webp' })} 400w,
${getImageUrl(post.cover, { width: 800, format: 'webp' })} 800w,
${getImageUrl(post.cover, { width: 1200, format: 'webp' })} 1200w
`}
sizes="(max-width: 768px) 100vw, 800px"
alt={post.title}
loading="lazy"
/>
九、认证与会话管理
9.1 基于 Cookie 的 Session
// app/server/session.ts
import { useSession } from '@tanstack/react-start/server'
const SESSION_SECRET = process.env.SESSION_SECRET!
export function useAppSession() {
return useSession<{ userId: number; email: string }>({
password: SESSION_SECRET,
cookie: {
name: 'session',
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 天
},
})
}
// 登录 Server Function
export const login = createServerFn({ method: 'POST' })
.inputValidator(z.object({
email: z.string().email(),
password: z.string().min(6),
}))
.handler(async ({ data, request }) => {
const env = request.context.cloudflare.env
const db = getDb(env)
// 验证用户
const user = await db.select()
.from(users)
.where(eq(users.email, data.email))
.get()
if (!user || !(await verifyPassword(data.password, user.passwordHash))) {
throw new Error('Invalid credentials')
}
// 创建 Session
const session = await useAppSession()
await session.update({ userId: user.id, email: user.email })
return { success: true, user: { id: user.id, name: user.name } }
})
// 获取当前用户
export const getCurrentUser = createServerFn({ method: 'GET' })
.handler(async () => {
const session = await useAppSession()
const userData = session.data
if (!userData?.userId) return null
// 可选:从 D1 获取最新用户信息
return { id: userData.userId, email: userData.email }
})
// 登出
export const logout = createServerFn({ method: 'POST' })
.handler(async () => {
const session = await useAppSession()
await session.clear()
return { success: true }
})
9.2 受保护路由
// app/routes/dashboard.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { getCurrentUser } from '~/server/functions'
export const Route = createFileRoute('/dashboard')({
component: DashboardPage,
beforeLoad: async () => {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login', search: { redirect: '/dashboard' } })
}
return { user }
},
})
function DashboardPage() {
const { user } = Route.useRouteContext()
return (
<div>
<h1>欢迎, {user.email}</h1>
<DashboardContent />
</div>
)
}
十、部署与运维
10.1 本地开发
# 1. 登录 Cloudflare
npx wrangler login
# 2. 创建本地 D1 数据库
npx wrangler d1 create my-local-db
# 3. 执行数据库迁移
npx wrangler d1 migrations apply my-local-db --local
# 4. 启动开发服务器(自动使用本地 bindings)
npm run dev
# 开发时访问 http://localhost:5173
10.2 生产部署
# 1. 构建项目
npm run build
# 2. 部署到 Cloudflare Workers
npm run deploy
# 或使用 wrangler 直接部署
npx wrangler deploy
10.3 环境变量管理
# .dev.vars(本地开发,不提交到 Git)
SESSION_SECRET=dev-secret-key-123
STRIPE_KEY=sk_test_...
# Cloudflare Dashboard > Workers & Pages > 你的项目 > Settings > Variables
# 生产环境变量在 Cloudflare Dashboard 中设置
10.4 监控与日志
在 wrangler.jsonc 中启用 observability:
{
"observability": {
"enabled": true,
"head_sampling_rate": 1 // 100% 采样
}
}
在 Server Functions 中记录结构化日志:
export const getPosts = createServerFn({ method: 'GET' })
.handler(async ({ request }) => {
const start = Date.now()
const env = request.context.cloudflare.env
try {
const posts = await env.DB.prepare('SELECT * FROM posts').all()
console.log(JSON.stringify({
event: 'get_posts',
duration: Date.now() - start,
count: posts.results.length,
status: 'success',
}))
return posts.results
} catch (error) {
console.error(JSON.stringify({
event: 'get_posts_error',
error: error.message,
duration: Date.now() - start,
}))
throw error
}
})
十一、性能优化
11.1 构建优化
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [
cloudflare({ viteEnvironment: { name: 'ssr' } }),
tanstackStart(),
],
build: {
// 代码分割策略
rollupOptions: {
output: {
manualChunks: {
// 将大型库单独打包
'react-vendor': ['react', 'react-dom'],
'tanstack': ['@tanstack/react-router', '@tanstack/react-query'],
'ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
})
11.2 流式 SSR
TanStack Start 支持 React 18 的流式 SSR,关键内容优先渲染:
// app/routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
component: PostPage,
loader: async ({ params }) => {
// 关键数据:立即获取
const post = await getPost({ data: { slug: params.postId } })
return { post }
},
})
function PostPage() {
const { post } = Route.useLoaderData()
return (
<div>
{/* 关键内容立即渲染 */}
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
{/* 非关键内容:Suspense 流式加载 */}
<Suspense fallback={<CommentsSkeleton />}>
<Comments postId={post.id} />
</Suspense>
</div>
)
}
11.3 边缘缓存策略
// 在 Server Functions 中设置缓存头
export const getPublicData = createServerFn({ method: 'GET' })
.handler(async () => {
setResponseHeaders(new Headers({
'Cache-Control': 'public, max-age=300, stale-while-revalidate=600',
'CDN-Cache-Control': 'max-age=3600',
}))
return fetchPublicData()
})
// 私有数据(用户相关)
export const getMyOrders = createServerFn({ method: 'GET' })
.handler(async () => {
const session = await requireSession()
setResponseHeaders(new Headers({
'Cache-Control': 'private, max-age=60',
'Vary': 'Cookie, Authorization',
}))
return db.orders.findMany({ where: { userId: session.userId } })
})
十二、常见问题与解决方案
12.1 构建时 Prerender 与 Bindings 冲突
问题:构建时 Prerender 会执行 loader,但此时 Cloudflare Bindings 不可用。
解决方案:
// 禁用特定路由的 Prerender
export const Route = createFileRoute('/dashboard')({
component: DashboardPage,
loader: async () => {
// 仅在服务端运行时执行
if (typeof window === 'undefined') {
// 构建时返回空数据或 mock 数据
return { posts: [] }
}
return { posts: await getPosts() }
},
})
// 或在 vite.config.ts 中全局禁用
export default defineConfig({
plugins: [...],
ssr: {
noExternal: true,
},
})
12.2 Node.js 兼容性问题
问题:某些 npm 包依赖 Node.js 内置模块(如 fs、path),在 Workers 运行时报错。
解决方案:
// wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat"]
}
若仍有问题,使用 wrangler 的 alias 功能或寻找替代库。
12.3 数据库连接池
D1 是 SQLite,无需连接池。但注意:D1 不支持并发写入,大量写入需排队处理。
// 使用队列处理批量写入
export const bulkCreatePosts = createServerFn({ method: 'POST' })
.handler(async ({ request }) => {
const env = request.context.cloudflare.env
// D1 批量插入
const stmt = await env.DB.prepare(
'INSERT INTO posts (title, content) VALUES (?, ?)'
)
// 使用 batch 批量执行
const batch = posts.map(post =>
stmt.bind(post.title, post.content)
)
await env.DB.batch(batch)
})
十三、总结与展望
13.1 技术栈总结
| 层级 | 技术 | 作用 |
|---|---|---|
| 框架 | TanStack Start | 全栈 React 框架,类型安全路由 + Server Functions |
| 路由 | TanStack Router | 文件系统路由,100% 类型安全 |
| 数据 | TanStack Query | 服务端状态管理,缓存、乐观更新、无限滚动 |
| 部署 | Cloudflare Workers | 边缘计算,全球低延迟 |
| 数据库 | D1 (SQLite) | 边缘关系型数据库 |
| 缓存 | KV | 全局键值存储,低延迟读取 |
| 存储 | R2 | S3 兼容对象存储 |
| ORM | Drizzle | 类型安全 SQL 构建器 |
| UI | shadcn/ui + Tailwind | 现代组件库 |
13.2 适用场景
✅ 推荐使用:
- 内容型网站(博客、文档、营销页)
- 中小型 SaaS(用户量 < 10 万)
- 需要全球低延迟的应用
- 类型安全要求高的团队
- 希望避免 Next.js RSC 复杂性的项目
⚠️ 谨慎使用:
- 超大规模应用(需要复杂微服务架构)
- 重度依赖 React Server Components 的项目
- 需要复杂实时协作的场景(考虑 Durable Objects)
13.3 2026 年展望
- TanStack Start v1.0:预计 2026 年中正式发布,API 已锁定
- React Server Components:TanStack Start 正在实验性支持 RSC
- AI 集成:TanStack Intent 已支持 Agent Skills,AI 助手可原生理解 TanStack 工具
- 更多部署目标:Appwrite、Fly.io 等新平台适配器正在开发中
附录:完整项目示例
GitHub 上的开源示例项目:
- tanstack-start-faster — 电商模板,含 D1 + KV + R2 完整集成
- start-basic-cloudflare — 官方基础示例