部署与生产环境
本教程共 38 篇 · 第 25 篇 · 更新于 2026-07-27 · 约 16 分钟阅读
25. 部署与生产环境
本节目标:学会把 TanStack Start 应用部署到生产环境。掌握平台选择、环境变量配置、中间件使用、SEO 优化、错误边界、可观测性。学完你能把应用安全稳定地上线,并且出问题时能快速定位。
25.1 部署平台选择
TanStack Start 设计上支持任何托管平台。官方推荐三个合作伙伴:Cloudflare、Netlify、Railway。
Cloudflare Workers
边缘计算平台,全球节点,延迟低。需要额外配置:
- 安装依赖:
npm install -D @cloudflare/vite-plugin wrangler
- 配置 Vite:
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
cloudflare({ viteEnvironment: { name: 'ssr' } }),
tanstackStart(),
viteReact(),
],
})
- 添加
wrangler.jsonc:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "tanstack-start-app",
"compatibility_date": "2025-09-02",
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry"
}
- 部署:
npx wrangler login # 登录
npm run build # 构建
npx wrangler deploy # 部署
WarningCloudflare Workers 是边缘运行时,
process.env在模块加载时为空(按请求注入)。环境变量必须在 handler 内部读取,不能在模块级别读。
Netlify
全栈平台,内置 CDN 和 Server Functions:
- 安装插件:
npm install -D @netlify/vite-plugin-tanstack-start
- 配置 Vite:
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import netlify from '@netlify/vite-plugin-tanstack-start'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
tanstackStart(),
netlify(),
viteReact(),
],
})
- 部署:
npx netlify deploy
或手动配置 netlify.toml:
[build]
command = "vite build"
publish = "dist/client"
[dev]
command = "vite dev"
port = 3000
Node.js / Docker
最通用的部署方式,用 Nitro 作为服务器层:
- 安装 Nitro:
npm install nitro
- 配置 Vite:
// vite.config.ts
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { defineConfig } from 'vite'
import { nitro } from 'nitro/vite'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [tanstackStart(), nitro(), viteReact()],
})
- 配置 scripts:
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"start": "node .output/server/index.mjs"
}
}
- 构建和启动:
npm run build
npm run start
TipNode.js 部署可以配合 Docker 打包成镜像,部署到任何容器平台(AWS ECS、Kubernetes、Fly.io 等)。
Vercel
使用 Nitro 部署,跟 Node.js 方式一样配 Nitro 插件,然后连接 Vercel 仓库自动部署。
Bun
Bun 运行时部署,性能更好(需要 React 19):
// vite.config.ts
export default defineConfig({
plugins: [tanstackStart(), nitro({ preset: 'bun' }), viteReact()],
})
bun run build
bun run server.ts
25.2 环境变量
服务端 vs 客户端
环境变量分两种:
- 服务端变量:无前缀,只能在 Server Functions 和服务端代码里用
- 客户端变量:Vite 用
VITE_前缀,Rsbuild 用PUBLIC_前缀,客户端能用
# .env
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb # 服务端
JWT_SECRET=super-secret-key # 服务端
VITE_APP_NAME=我的应用 # 客户端
VITE_API_URL=https://api.example.com # 客户端
服务端读取
import { createServerFn } from '@tanstack/react-start'
const getUser = createServerFn().handler(async () => {
// 直接用 process.env,任何变量都行
const db = await connect(process.env.DATABASE_URL)
return db.user.findFirst()
})
客户端读取
function AppHeader() {
// 只能读 VITE_ 前缀的
return <h1>{import.meta.env.VITE_APP_NAME}</h1>
}
Warning
import.meta.env.DATABASE_URL在客户端是undefined。这是安全特性,防止密钥泄漏。不要给敏感变量加VITE_前缀。
环境文件层级
Start 自动按顺序加载:
.env.local # 本地覆盖(加入 .gitignore)
.env.production # 生产环境
.env.development # 开发环境
.env # 默认值(提交到 git)
后面的文件覆盖前面的同名变量。
按请求读取(边缘运行时)
NoteCloudflare Workers 等边缘运行时按请求注入环境变量。模块加载时
process.env还是空的。必须在 handler 内部读:
// ❌ 模块级别读取(边缘运行时会 undefined)
const apiKey = process.env.API_SECRET
// ✅ 在 handler 内读取
const getData = createServerFn().handler(async () => {
const apiKey = process.env.API_SECRET
return fetchExternalData(apiKey)
})
类型安全
用 TypeScript 声明和 Zod 校验:
// src/env.d.ts
interface ImportMetaEnv {
readonly VITE_APP_NAME: string
readonly VITE_API_URL: string
readonly VITE_SENTRY_DSN?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
declare global {
namespace NodeJS {
interface ProcessEnv {
readonly DATABASE_URL: string
readonly JWT_SECRET: string
readonly STRIPE_SECRET_KEY: string
readonly NODE_ENV: 'development' | 'production' | 'test'
}
}
}
25.3 中间件
中间件用来处理认证、日志、CORS 等,在 Server Functions 和 API 路由之前执行。
两种中间件
| 类型 | 作用范围 | 方法 |
|---|---|---|
| 请求中间件 | 所有服务端请求 | .server() |
| Server Function 中间件 | 仅 Server Functions | .client() + .server() |
创建请求中间件
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(
async ({ next, request }) => {
console.log(`请求: ${request.method} ${request.url}`)
const startTime = Date.now()
const result = await next()
const duration = Date.now() - startTime
console.log(`响应: ${duration}ms`)
return result
},
)
认证中间件
const authMiddleware = createMiddleware().server(
async ({ next, context }) => {
// 从请求中获取 token
const token = getRequestHeader('authorization')?.replace('Bearer ', '')
if (!token) {
throw new Error('未认证')
}
const user = await verifyToken(token)
// 注入到上下文,后续中间件和 handler 能用
return next({ context: { user } })
},
)
在 Server Function 上使用
export const getProfile = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context: { user } }) => {
// user 从 authMiddleware 的上下文来,类型安全
return fetchUserProfile(user.id)
})
全局注册
在 src/start.ts 里注册全局请求中间件:
import { createStart } from '@tanstack/react-start'
export const startInstance = createStart(() => ({
requestMiddleware: [loggingMiddleware, csrfMiddleware],
}))
Tip中间件可以组合。一个中间件可以依赖另一个,形成链式执行。顺序很重要:认证 -> 日志 -> 实际处理。
25.4 SEO 优化
head 管理
路由的 head 属性控制页面 head 标签:
// src/routes/index.tsx
export const Route = createFileRoute('/')({
head: () => ({
meta: [
{ title: '我的应用 - 首页' },
{ name: 'description', content: '欢迎来到我的应用' },
],
}),
component: HomePage,
})
动态 meta 标签
用 loader 数据生成动态 SEO 信息:
// src/routes/posts/$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
head: ({ loaderData }) => ({
meta: [
{ title: loaderData.post.title },
{ name: 'description', content: loaderData.post.excerpt },
],
}),
component: PostPage,
})
Open Graph 和社交分享
head: ({ loaderData }) => ({
meta: [
{ title: loaderData.post.title },
{ name: 'description', content: loaderData.post.excerpt },
// Open Graph
{ property: 'og:title', content: loaderData.post.title },
{ property: 'og:description', content: loaderData.post.excerpt },
{ property: 'og:image', content: loaderData.post.coverImage },
{ property: 'og:type', content: 'article' },
// Twitter Card
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: loaderData.post.title },
{ name: 'twitter:image', content: loaderData.post.coverImage },
],
}),
NoteStart 默认做 SSR,搜索引擎能拿到完整渲染的 HTML。配合静态预渲染,SEO 效果更好。
根路由 head
在根路由设置全局 head:
// src/routes/__root.tsx
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: '我的应用' },
{ name: 'description', content: '应用描述' },
],
}),
})
子路由的 head 会合并到根路由的 head 里。
25.5 错误边界
全局默认错误组件
在路由器上设置:
// src/router.tsx
import { createRouter, ErrorComponent } from '@tanstack/react-router'
export function getRouter() {
const router = createRouter({
routeTree,
defaultErrorComponent: ({ error, reset }) => (
<ErrorComponent error={error} />
),
})
return router
}
按路由自定义
// src/routes/posts.$postId.tsx
import { createFileRoute, ErrorComponent } from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'
function PostError({ error, reset }: ErrorComponentProps) {
return (
<div>
<p>加载文章失败:{error.message}</p>
<button onClick={reset}>重试</button>
</div>
)
}
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
errorComponent: PostError,
})
loader 错误处理
loader 抛的错误会被错误边界捕获:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
if (!post) {
throw new Error('文章不存在')
}
return { post }
},
errorComponent: PostError,
component: PostPage,
})
Tip
reset()会重置错误边界。如果是 loader 出错,用router.invalidate()更好,它会重新执行 loader 并重置错误边界。
25.6 可观测性
Sentry 集成
官方推荐 Sentry 做错误追踪和性能监控:
客户端:
// src/app.tsx
import * as Sentry from '@sentry/react'
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: process.env.NODE_ENV,
})
服务端:
import * as Sentry from '@sentry/node'
const getUser = createServerFn().handler(async () => {
try {
return await riskyOperation()
} catch (error) {
Sentry.captureException(error)
throw error
}
})
请求日志中间件
用中间件统一记录所有请求:
import { createMiddleware } from '@tanstack/react-start'
const requestLogger = createMiddleware().server(
async ({ request, next }) => {
const startTime = Date.now()
const { method, url } = request
try {
const result = await next()
const duration = Date.now() - startTime
console.log(`[${method}] ${url} - ${duration}ms`)
return result
} catch (error) {
const duration = Date.now() - startTime
console.error(`[${method}] ${url} - ERROR after ${duration}ms`, error)
throw error
}
},
)
Server Function 日志
const getUser = createServerFn({ method: 'GET' })
.validator((id: string) => id)
.handler(async ({ data: id }) => {
const startTime = Date.now()
console.log(`[SERVER] 获取用户 ${id}`)
try {
const user = await db.users.findUnique({ where: { id } })
const duration = Date.now() - startTime
console.log(`[SERVER] 用户 ${id} 获取完成,耗时 ${duration}ms`)
return user
} catch (error) {
const duration = Date.now() - startTime
console.error(`[SERVER] 用户 ${id} 获取失败,耗时 ${duration}ms`, error)
throw error
}
})
25.7 生产环境检查清单
上线前对照检查:
环境变量:
- 所有敏感变量没有
VITE_/PUBLIC_前缀 -
.env.local已加入.gitignore - 生产环境变量已配置到托管平台
- 必需变量在启动时校验
- 源码里没有硬编码密钥
安全:
- Server Functions 有 CSRF 保护(
createCsrfMiddleware) - 认证数据缓存头用
private - Server Function 输入有校验(
validator) - 敏感操作的 Server Function 有认证中间件
性能:
- 静态页面做了预渲染
- CDN 缓存头配置正确
- 大页面用了 deferred-hydration
- 数据库连接用了连接池
可观测性:
- 错误追踪已接入(Sentry 等)
- 请求日志中间件已配置
- 关键操作有日志记录
- 生产环境
NODE_ENV=production
部署:
-
npm run build能成功 - 构建产物检查过(没有服务端代码泄漏到客户端)
- 部署后页面能正常访问
- Server Functions 能正常调用
25.8 小结
这是 TanStack 生态教程 Router + Start 部分的最后一章:
- 部署平台:Cloudflare Workers(边缘)、Netlify(全栈)、Node.js/Docker(通用)、Vercel、Bun
- 环境变量:无前缀是服务端专用,
VITE_前缀客户端可用,边缘运行时按请求读取 - 中间件:请求中间件处理所有请求,Server Function 中间件处理 RPC,可组合可链式
- SEO:
head属性管理 meta 标签,支持动态数据和 Open Graph - 错误边界:全局
defaultErrorComponent+ 按路由errorComponent - 可观测性:Sentry 集成 + 请求日志中间件 + Server Function 日志
整个 TanStack 生态教程到这就收尾了。从 Query 的数据获取、Router 的类型安全路由、Start 的全栈能力,一路走下来该讲的都讲了。剩下的就是动手练—这些库我用了好几年,最大的感受是「类型安全这东西,用过就回不去」。希望这些内容能帮到你。