Skip to content

参考

基于 TanStack Start v1.x RC(@tanstack/react-start + @tanstack/react-router)—— API 速查 / 文件约定 / 配置选项 / 命名约定。

包结构

用途必需
@tanstack/react-startStart 运行时 + createServerFn / createMiddleware / createStart
@tanstack/react-router路由核心(createFileRoute / Link / hooks)
@tanstack/react-start/plugin/viteVite 插件 tanstackStart()
@tanstack/react-start/server服务端工具(useSession / getRequest / setResponseHeaders 等)用到时
@tanstack/react-start/server-only标记服务端独占模块用到时
@tanstack/react-start/client-only标记客户端独占模块用到时
@tanstack/zod-adapterZod 适配器(zodValidatorZod 用户
@tanstack/react-queryTanStack Query(数据缓存)可选
@tanstack/react-router-devtools路由 devtoolsdev only
@cloudflare/vite-pluginCloudflare Workers 适配CF 部署
@netlify/vite-plugin-tanstack-startNetlify 适配Netlify 部署
nitro / nitro/vite通用部署 adapter(Vercel / AWS / Bun 等)多平台

CLI(@tanstack/cli

命令用途
npx @tanstack/cli@latest create创建新项目(交互式选择模板 / 包管理器 / 特性)
npx @tanstack/cli@latest add <plugin>添加官方插件(Tailwind / shadcn 等)

部署相关命令视平台不同(pnpm run deploy / netlify deploy / wrangler deploy 等)。

vite.config.ts —— tanstackStart() 选项

ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'

export default defineConfig({
  server: { port: 3000 },
  resolve: { tsconfigPaths: true },
  plugins: [
    tanstackStart({
      // ── 路由生成 ──
      tsr: {
        routesDirectory: 'src/routes',           // 路由目录(默认)
        generatedRouteTree: 'src/routeTree.gen.ts', // 生成文件路径
        autoCodeSplitting: true,                 // 自动 code-splitting(默认 true)
        quoteStyle: 'single',                    // 生成代码的引号风格
        semicolons: false,                       // 生成代码是否带分号
      },

      // ── Server functions ──
      server: {
        // Server function ID 生成策略(影响生产环境的端点 URL 稳定性)
        functionIdStrategy: 'hashed',  // 'hashed' | 'filename'
      },

      // ── SPA 模式 ──
      spa: {
        enabled: false,
        // 当 enabled=true 时,仅渲染 _shell.html
      },

      // ── Static prerendering(SSG) ──
      prerender: {
        enabled: false,
        autoSubfolderIndex: true,            // /about → /about/index.html
        autoStaticPathsDiscovery: true,
        concurrency: 14,
        crawlLinks: true,                    // 顺着 <Link> 爬取
        filter: ({ path }) => true,
        retryCount: 2,
        retryDelay: 1000,
        maxRedirects: 5,
        failOnError: true,
        onSuccess: ({ page }) => {},
        pages: [
          {
            path: '/specials',
            prerender: { enabled: true, outputPath: '/specials/index.html' },
          },
        ],
      },

      // ── 入口自定义 ──
      // 默认 entry: '@tanstack/react-start/server-entry'
      // 平台 adapter 可能覆盖

      // ── 导入保护严格度 ──
      // dev: 'warn'(默认)/ prod: 'error'
    }),
    viteReact(),
  ],
})

Router 类型注册

ts
// src/router.tsx
declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof getRouter>
  }
}

注册后:

  • Link.to 字符串获得整棵路由树自动补全
  • useNavigate / useParams / useSearch 获得对应路由的类型推导
  • <Link to>paramsparams 类型错时编译报错

createRouter 选项

ts
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({
  routeTree,

  // ── Preload ──
  defaultPreload: 'intent',        // 'intent' | 'viewport' | false
  defaultPreloadStaleTime: 30_000, // preload 缓存有效期
  defaultPreloadDelay: 50,         // intent 触发延迟(ms)
  defaultPreloadGcTime: 30 * 60_000,

  // ── 加载 ──
  defaultStaleTime: 0,             // 路由数据 fresh 时长(默认 0 → 每次重跑)
  defaultGcTime: 30 * 60_000,      // 缓存回收时长(默认 30min)

  // ── Pending ──
  defaultPendingMs: 1000,          // 1s 后才显示 pendingComponent
  defaultPendingMinMs: 500,        // pendingComponent 显示至少 500ms

  // ── 组件 ──
  defaultComponent: () => <Outlet />,
  defaultPendingComponent: () => <div>Loading...</div>,
  defaultErrorComponent: ({ error, reset }) => <p>{error.message}</p>,
  defaultNotFoundComponent: () => <h1>404</h1>,

  // ── 上下文 ──
  context: { queryClient },        // 与 createRootRouteWithContext 配套

  // ── Wrap:在 RouterProvider 外层包一层 ──
  Wrap: ({ children }) => <QueryClientProvider client={qc}>{children}</QueryClientProvider>,

  // ── InnerWrap:在路由组件外层包一层(如 i18n) ──
  InnerWrap: ({ children }) => <I18nProvider>{children}</I18nProvider>,

  // ── History ──
  basepath: '/app',                // 全应用前缀(默认 '/')
  scrollRestoration: true,         // 自动恢复滚动位置(默认 false)

  // ── 错误时的行为 ──
  defaultOnCatch: ({ error }) => { /* 全局 catch */ },
})

createFileRoute() 选项

tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/path/$param')({
  // ── 组件 ──
  component: MyComponent,              // 路由组件
  errorComponent: ({ error, reset }) => ...,
  pendingComponent: () => <Loading />,
  notFoundComponent: () => <NotFound />,

  // ── 数据加载 ──
  loader: async ({ params, deps, context, abortController, preload, location, cause }) => {...},
  loaderDeps: ({ search }) => ({ ... }),    // 返回 loader 的 deps
  beforeLoad: async ({ context, location, params, search }) => { return { extra: '...' } },

  // ── 缓存 ──
  staleTime: 10_000,
  preloadStaleTime: 30_000,
  gcTime: 30 * 60_000,
  shouldReload: false,                 // 是否在 deps 不变时重跑

  // ── 渲染策略 ──
  pendingMs: 500,
  pendingMinMs: 300,

  // ── SSR 控制 ──
  ssr: true,                           // true | false | 'data-only' | ({ params, search }) => ...

  // ── Search params ──
  validateSearch: SearchSchema,        // Zod schema 或函数 (s: unknown) => MySearch
  search: {
    middlewares: [
      retainSearchParams(['lang']),
      stripSearchParams({ page: 1 }),
    ],
  },

  // ── Server routes(API 端点) ──
  server: {
    middleware: [authMw],
    handlers: {
      GET: async ({ request, params, context }) => Response.json({...}),
      POST: ...,
      PUT: ...,
      PATCH: ...,
      DELETE: ...,
    },
    // 或带逐方法 middleware:
    // handlers: ({ createHandlers }) => createHandlers({...})
  },

  // ── Head ──
  head: () => ({
    meta: [{ title: '页面标题' }, { name: 'description', content: '...' }],
    links: [{ rel: 'canonical', href: '...' }],
    scripts: [{ src: '...' }],
  }),

  // ── Lifecycle hooks ──
  onEnter: (match) => {},
  onStay: (match) => {},
  onLeave: (match) => {},
  onError: ({ error }) => {},
  onCatch: ({ error, errorInfo }) => {},
})

createRootRoute / createRootRouteWithContext

tsx
import { createRootRoute, createRootRouteWithContext } from '@tanstack/react-router'

// 不带上下文
export const Route = createRootRoute({
  head: () => ({ meta: [{ title: 'App' }] }),
  component: RootComponent,
})

// 带上下文(用于 QueryClient / auth user / 等全局)
interface MyContext {
  queryClient: QueryClient
  user?: User
}

export const Route = createRootRouteWithContext<MyContext>()({
  head: () => ({...}),
  component: RootComponent,
})

Components

<HeadContent />

<head> 内渲染所有路由的 head 内容(meta / title / link / script)。

tsx
<head>
  <HeadContent />
</head>

<Scripts />

注入客户端 JS bundle / hydration 数据,必须在 <body> 末尾。

tsx
<body>
  <Outlet />
  <Scripts />
</body>

<Outlet />

渲染下一级匹配的子路由(或 null)。

tsx
function Layout() {
  return (
    <div>
      <Header />
      <Outlet />
      <Footer />
    </div>
  )
}

类型安全的导航链接。

tsx
<Link
  to="/posts/$postId"
  params={{ postId: '1' }}
  search={{ q: 'react' }}
  hash="comments"
  replace={false}                       // 默认 false
  preload="intent"                      // 'intent' | 'viewport' | false
  activeProps={{ className: 'active' }}
  inactiveProps={{ className: 'inactive' }}
  activeOptions={{ exact: false, includeHash: false, includeSearch: false }}
  resetScroll={true}
  startTransition={true}
  ignoreBlocker={false}                 // 跳过 useBlocker
  reloadDocument={false}                // 强制完整重载
  from="/current/path"                  // 相对路径基准
  search={prev => ({ ...prev, page: prev.page + 1 })} // 函数式
>
  Post 1
</Link>

<ClientOnly>

仅在客户端渲染(server 端显示 fallback)。

tsx
import { ClientOnly } from '@tanstack/react-router'

<ClientOnly fallback={<div>Loading...</div>}>
  <ThirdPartyMap />
</ClientOnly>

声明式跳转(少用,多用 redirect() / useNavigate)。

tsx
<Navigate to="/login" replace />

<MatchRoute>

条件渲染(基于路由匹配)。

tsx
<MatchRoute to="/posts/$postId" params={{ postId: '1' }}>
  {(match) => match ? <div>当前是 Post 1</div> : null}
</MatchRoute>

<ErrorComponent>

默认错误组件(用于 fallback / 自定义错误组件包装)。

tsx
import { ErrorComponent } from '@tanstack/react-router'

errorComponent: ({ error }) => <ErrorComponent error={error} />

Hooks

路由内(Route.useXxx()

tsx
const params = Route.useParams()         // 当前路由的 params(typed)
const search = Route.useSearch()         // 当前路由的 search(typed)
const data = Route.useLoaderData()       // loader 返回值
const ctx = Route.useRouteContext()      // 当前路由的 context
const match = Route.useMatch()           // 当前路由的 match 信息

路由外(getRouteApi

tsx
import { getRouteApi } from '@tanstack/react-router'

const api = getRouteApi('/posts/$postId')

function Comp() {
  const { postId } = api.useParams()
  const post = api.useLoaderData()
}

全局 hooks

Hook用途
useRouter()获取 router 实例(router.invalidate() / router.navigate()
useNavigate()编程式导航函数
useLocation()当前 location(pathname / search / hash)
useMatches()所有匹配路由的 match 数组
useMatch()单个 match(按 routeId 查找)
useChildMatches()当前路由的子 match 数组
useParentMatches()当前路由的父 match 数组
useRouterState()完整 router state(包含 transition state)
useBlocker()拦截导航(如未保存提示)
useCanGoBack()是否可后退
useSearch({ from })任意路由的 search
useParams({ from })任意路由的 params
useLoaderData({ from })任意路由的 loader data
useLoaderDeps({ from })任意路由的 loader deps
useRouteContext({ from })任意路由的 context

函数 / 工具

导航 / 重定向

tsx
import { redirect, notFound } from '@tanstack/react-router'

// 抛出重定向
throw redirect({ to: '/login', search: { redirect: location.href } })

// 抛出 404
throw notFound()

// 抛出业务错误(被 errorComponent 捕获)
throw new Error('something failed')

linkOptions()

tsx
import { linkOptions } from '@tanstack/react-router'

export const homeLink = linkOptions({
  to: '/',
})

export const postLink = (id: string) =>
  linkOptions({
    to: '/posts/$postId',
    params: { postId: id },
  })

// 使用
<Link {...homeLink}>Home</Link>
<Link {...postLink('1')}>Post 1</Link>

retainSearchParams() / stripSearchParams()

tsx
import { retainSearchParams, stripSearchParams } from '@tanstack/react-router'

search: {
  middlewares: [
    retainSearchParams(['lang', 'theme']),
    stripSearchParams({ page: 1, sort: 'newest' }),
  ],
}

useBlocker

tsx
import { useBlocker } from '@tanstack/react-router'

useBlocker({
  shouldBlockFn: () => isDirty,
  withResolver: false,
  enableBeforeUnload: true,
})

createServerFn API

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

const fn = createServerFn({
  method: 'POST',         // 'GET' | 'POST'(默认 GET)
  strict: true,           // 序列化检查(默认 true)
  // strict: { input: false } —— 仅关闭输入检查
  type: 'static',         // 'dynamic' | 'static'(static = 构建期执行)
})
  .inputValidator(schemaOrFn)         // 输入校验
  .middleware([mw1, mw2])             // 中间件链
  .handler(async ({ data, context, signal }) => {
    return { /* 返回值,必须可序列化 */ }
  })

// 客户端调用
await fn({ data: {...}, headers: {...}, fetch: customFetch })

useServerFn

tsx
import { useServerFn } from '@tanstack/react-start'

const myFn = useServerFn(originalFn) // 返回包装后的可调用函数

createMiddleware API

tsx
import { createMiddleware } from '@tanstack/react-start'

// Request middleware(默认)
const reqMw = createMiddleware()
  .middleware([dep1])
  .server(async ({ next, request, context }) => {
    return next({ context: { ... } })
  })

// Function middleware(仅 createServerFn 用)
const fnMw = createMiddleware({ type: 'function' })
  .inputValidator(schema)
  .middleware([dep1])
  .client(async ({ next, context }) => {
    return next({
      sendContext: { /* 显式送到 server */ },
      headers: { /* 自定义 header */ },
      fetch: customFetch,
    })
  })
  .server(async ({ next, context, data, request }) => {
    return next({ context: { ... }, sendContext: { /* 回传 client */ } })
  })

createStart —— 全局配置

tsx
// src/start.ts
import { createStart, createMiddleware, createCsrfMiddleware } from '@tanstack/react-start'

const csrfMw = createCsrfMiddleware({
  filter: ctx => ctx.handlerType === 'serverFn',
  origin: 'https://app.example.com', // 可选
})

const globalLog = createMiddleware().server(async ({ next, request }) => {
  console.log(request.method, request.url)
  return next()
})

export const startInstance = createStart(() => ({
  requestMiddleware: [csrfMw, globalLog],
  functionMiddleware: [/* server function 专属全局 mw */],
  defaultSsr: true,                  // 全局 SSR 默认值
  serverFns: {
    fetch: customFetch,              // 全局 fetch 实现
  },
}))

Server-side 工具(@tanstack/react-start/server

tsx
import {
  getRequest,
  getRequestHeader,
  getRequestHeaders,
  setResponseHeaders,
  setResponseHeader,
  setResponseStatus,
  useSession,
} from '@tanstack/react-start/server'

// 拿请求
const req = getRequest()
const auth = getRequestHeader('authorization')

// 改响应
setResponseHeader('X-Foo', 'bar')
setResponseHeaders(new Headers({ 'Cache-Control': '...' }))
setResponseStatus(201)

// Session
const session = await useSession<{ userId: string }>({
  name: 'app-session',
  password: process.env.SESSION_SECRET!,
  cookie: { httpOnly: true, secure: true, sameSite: 'lax' },
})

await session.update({ userId: 'u1' })  // 设置
session.data.userId                      // 读取
await session.clear()                    // 清空

同构 / 独占 API(@tanstack/react-start

tsx
import {
  createServerFn,         // RPC(推荐)
  createServerOnlyFn,     // 仅服务端工具(客户端调用抛错)
  createClientOnlyFn,     // 仅客户端工具
  createIsomorphicFn,     // 双端不同实现
  useHydrated,            // hydration 状态 hook
} from '@tanstack/react-start'

// 仅服务端
const getEnv = createServerOnlyFn(() => process.env.SECRET)

// 仅客户端
const saveLocal = createClientOnlyFn((k: string, v: any) =>
  localStorage.setItem(k, JSON.stringify(v)),
)

// 双端不同实现
const getInfo = createIsomorphicFn()
  .server(() => ({ env: 'server', platform: process.platform }))
  .client(() => ({ env: 'client', ua: navigator.userAgent }))

文件命名约定速查

模式含义URL
__root.tsx根路由(始终匹配)
index.tsx父 index/parent/
name.tsx静态段/name
$param.tsx动态参数/123params.param = '123'
$.tsxSplat/a/b/cparams._splat = 'a/b/c'
_layout.tsxPathless 布局(包裹子路由)(无 URL)
(group)/x.tsxPathless group(仅组织)/x
-name.tsx排除(不生成路由)(无)
foo.bar.tsx扁平嵌套/foo/bar
foo_.bar.tsx非嵌套(不继承父)/foo/bar(独立树)
foo[.]bar.tsx转义点号/foo.bar
foo/route.tsx文件夹下的父路由/foo
{-$param}.tsx可选参数/posts/posts/tech

routeTree.gen.ts

Vite 插件自动生成的文件,包含:

  • 整棵路由树的运行时(routeTree
  • 全应用路径字符串 / params / search 的类型定义
  • Register 接口扩展
ts
// 由插件自动生成 —— 切勿手动编辑
import { Route as IndexRoute } from './routes/index'
import { Route as AboutRoute } from './routes/about'
import { Route as PostsRoute } from './routes/posts'
// ...

export const routeTree = rootRoute.addChildren([
  IndexRoute,
  AboutRoute,
  PostsRoute.addChildren([/* ... */]),
])

declare module '@tanstack/react-router' {
  interface FileRoutesByPath {
    '/': { /* ... */ }
    '/about': { /* ... */ }
    '/posts/$postId': { /* ... */ }
  }
}

通常加入 .gitignore,dev / build 时自动重新生成。

项目结构约定

my-app/
├── package.json
├── tsconfig.json
├── vite.config.ts                    # Vite + tanstackStart() 插件
├── src/
│   ├── router.tsx                    # createRouter + Register 类型注册
│   ├── routeTree.gen.ts              # ⚙️ 自动生成(gitignore)
│   ├── start.ts                      # (可选)createStart + 全局中间件 + CSRF
│   ├── env.d.ts                      # 环境变量类型
│   ├── routes/
│   │   ├── __root.tsx                # 根路由 + HTML 壳
│   │   ├── index.tsx                 # / 路由
│   │   ├── _authed.tsx               # 鉴权 layout(pathless)
│   │   ├── _authed/
│   │   │   ├── dashboard.tsx         # /dashboard
│   │   │   └── settings.tsx          # /settings
│   │   ├── posts/
│   │   │   ├── index.tsx             # /posts
│   │   │   └── $postId.tsx           # /posts/:id
│   │   └── api/
│   │       └── users.ts              # /api/users (API only)
│   ├── utils/
│   │   ├── users.functions.ts        # createServerFn 包装
│   │   ├── users.server.ts           # server-only 工具
│   │   └── schemas.ts                # 共享 Zod schema
│   ├── middleware/
│   │   ├── auth.ts                   # 认证中间件
│   │   └── authz.ts                  # 授权中间件
│   ├── components/                   # 共享组件
│   └── styles.css
├── .env                              # 共享 env
├── .env.development                  # dev 环境 env
├── .env.production
└── .env.local                        # 本地覆盖(gitignore)

默认端口 / URL

用途URL / 端口
Dev serverhttp://localhost:3000
Server function endpoint/_serverFn/<hashed-id>
API routes按文件路径(如 /api/users
SPA shell/_shell.html
Devtools 浮按钮默认右下角(dev 模式)

构建产物

.output/
├── public/             # 静态资源(HTML / CSS / JS / 图片)
│   ├── index.html      # SPA shell(仅 SPA / SSG 模式)
│   ├── assets/
│   │   ├── *.js
│   │   └── *.css
│   └── *.html          # SSG 静态 HTML
└── server/
    ├── index.mjs       # Node.js server entry
    └── chunks/

错误码 / 异常

异常触发处理
redirect({...})loader / beforeLoad / server fn 内抛自动跳转
notFound()同上显示 notFoundComponent
Error任意位置抛显示 errorComponent
400 / 422server fn 输入校验失败client 端 throw
401 / 403CSRF / auth middleware 抛client 端 throw

与 TanStack Query 协作 API

tsx
import { QueryClient, queryOptions, useSuspenseQuery, useMutation } from '@tanstack/react-query'

// 在 createRouter 时注入 client
const queryClient = new QueryClient()
const router = createRouter({
  routeTree,
  context: { queryClient },
  defaultPreloadStaleTime: 0,                 // 让 Query 接管缓存
  Wrap: ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  ),
})

// 定义 queryOptions(可复用)
const postsQuery = queryOptions({
  queryKey: ['posts'],
  queryFn: () => fetchPosts(),
})

// loader 预取
loader: ({ context }) => context.queryClient.ensureQueryData(postsQuery)

// 组件消费
const { data } = useSuspenseQuery(postsQuery)

ImportProtection 标记

ts
// 服务端独占(如果文件没有 .server.ts 后缀)
import '@tanstack/react-start/server-only'

// 客户端独占(如果文件没有 .client.ts 后缀)
import '@tanstack/react-start/client-only'

环境变量约定

bash
# 服务端独占(无前缀)
DATABASE_URL=postgresql://...
JWT_SECRET=secret-32-chars-min
SESSION_SECRET=another-secret

# 客户端可见(VITE_ 前缀)
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My App
VITE_SENTRY_DSN=...

读取:

  • 服务端:process.env.X —— 必须在 .handler() 内读(避免 edge 平台坑)
  • 客户端:import.meta.env.VITE_X

SSR 控制速查

设置beforeLoadloaderComponent HTML
ssr: trueserverserverserver
ssr: 'data-only'serverserverclient only
ssr: falseclientclientclient only
ssr: fn(...)函数返回值决定同上同上

继承:子路由只能让 SSR 更严格(true → 'data-only' → false),不能放松。

Server Routes Handler 上下文

tsx
GET: async ({
  request,       // 标准 Request 对象
  params,        // 动态路径参数
  context,       // middleware 注入的上下文
}) => {
  return new Response('...')
  // 或
  return Response.json({...})
  // 或
  return new Response(stream, { headers: { 'Content-Type': 'text/event-stream' } })
}

支持的 HTTP method:GET / POST / PUT / PATCH / DELETE / OPTIONS / HEAD

部署 Preset(Nitro)速查

平台preset备注
VercelvercelEdge / Node runtime
Cloudflare Workerscloudflare(用 @cloudflare/vite-plugin 更佳)nodejs_compat
Cloudflare Pagescloudflare-pages
Netlifynetlify(用 @netlify/vite-plugin-tanstack-start 更佳)
AWS Lambdaaws-lambdaAPI Gateway
Bunbun需要 React 19
Nodenode-server(默认)通用 Node
Denodeno-server
StaticstaticSSG only

调试

Router Devtools

dev 模式下右下角浮动按钮:

  • Routes:当前匹配链 / 全部路由树
  • Search:当前 search params + middleware 链
  • Matches:每个 match 的 params / loader data / context
  • Cache:SWR 缓存状态

路由 ID 日志

tsx
const router = createRouter({
  routeTree,
  // 显示路由调试信息
  notFoundMode: 'fuzzy', // 'fuzzy' | 'root'
})

Server function 调用日志

dev 时浏览器 Network 面板看 /_serverFn/... 请求:

  • Request:JSON 化的 data 输入
  • Response:JSON 化的返回值
  • Status:400 = 输入校验失败 / 403 = CSRF / 500 = handler 抛错

升级路径(RC → v1)

  • 锁定具体 minor 版本:"@tanstack/react-start": "1.0.0-rc.X"
  • 关注 CHANGELOG
  • 部分 unstable 特性:
    • RSC(experimental)
    • unstable_* 前缀的配置项

学习资源