tcbs-stack

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TCBS Stack

TCBS Stack

Build production-ready full-stack TypeScript applications with TanStack Start + Convex + Better-Auth + Shadcn UI.
使用TanStack Start + Convex + Better-Auth + Shadcn UI构建可用于生产环境的全栈TypeScript应用。

Stack Overview

技术栈概述

LayerTechnologyPurpose
FrontendTanStack StartFull-stack React framework with SSR
BackendConvexReal-time database & serverless functions
AuthBetter-AuthFlexible authentication with Convex adapter
UIShadcn UIAccessible component library
层级技术用途
前端TanStack Start支持SSR的全栈React框架
后端Convex实时数据库与无服务器函数
认证Better-Auth支持Convex适配器的灵活认证方案
UI组件Shadcn UI无障碍组件库

Quick Start

快速开始

bash
undefined
bash
undefined

Create new TanStack Start project with Shadcn (recommended)

创建带Shadcn的新TanStack Start项目(推荐)

bun create @tanstack/start@latest --tailwind --add-ons shadcn cd my-app
bun create @tanstack/start@latest --tailwind --add-ons shadcn cd my-app

Add Convex (requires v1.25.0+)

添加Convex(需要v1.25.0+版本)

bun add convex@latest @convex-dev/react-query bunx convex dev --once
bun add convex@latest @convex-dev/react-query bunx convex dev --once

Add Better-Auth with Convex

添加带Convex的Better-Auth

bun add better-auth @convex-dev/better-auth

**Alternative manual setup:**
```bash
bun create @tanstack/start@latest
bun add tailwindcss @tailwindcss/vite
bunx shadcn@latest init
bun add better-auth @convex-dev/better-auth

**手动安装备选方案:**
```bash
bun create @tanstack/start@latest
bun add tailwindcss @tailwindcss/vite
bunx shadcn@latest init

Project Structure

项目结构

project/
├── src/
│   ├── lib/
│   │   ├── auth-client.tsx      # Client auth setup
│   │   ├── auth-server.ts       # Server auth handler
│   │   └── utils.ts             # cn() helper
│   ├── routes/
│   │   ├── __root.tsx           # Root layout + providers
│   │   ├── _authed.tsx          # Protected route guard
│   │   ├── _authed/
│   │   │   └── index.tsx        # Dashboard
│   │   ├── sign-in.tsx
│   │   └── sign-up.tsx
│   ├── components/
│   │   └── ui/                  # Shadcn components
│   └── router.tsx
├── convex/
│   ├── auth.ts                  # Better-Auth instance
│   ├── auth.config.ts           # Auth providers config
│   ├── http.ts                  # HTTP routes
│   ├── schema.ts                # Database schema
│   └── convex.config.ts         # App config
├── components.json              # Shadcn config
└── vite.config.ts
project/
├── src/
│   ├── lib/
│   │   ├── auth-client.tsx      # 客户端认证配置
│   │   ├── auth-server.ts       # 服务端认证处理器
│   │   └── utils.ts             # cn()工具函数
│   ├── routes/
│   │   ├── __root.tsx           # 根布局 + 提供者
│   │   ├── _authed.tsx          # 受保护路由守卫
│   │   ├── _authed/
│   │   │   └── index.tsx        # 仪表盘
│   │   ├── sign-in.tsx
│   │   └── sign-up.tsx
│   ├── components/
│   │   └── ui/                  # Shadcn组件
│   └── router.tsx
├── convex/
│   ├── auth.ts                  # Better-Auth实例
│   ├── auth.config.ts           # 认证提供者配置
│   ├── http.ts                  # HTTP路由
│   ├── schema.ts                # 数据库 schema
│   └── convex.config.ts         # 应用配置
├── components.json              # Shadcn配置
└── vite.config.ts

Core Setup Files

核心配置文件

1. Convex Configuration

1. Convex配置

convex/convex.config.ts
typescript
import { defineApp } from "convex/server";
import betterAuth from "@convex-dev/better-auth/convex.config";

const app = defineApp();
app.use(betterAuth);
export default app;
convex/auth.config.ts
typescript
import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
import { AuthConfig } from "@auth/core";

export default {
  providers: [getAuthConfigProvider()],
} satisfies AuthConfig;
convex/auth.ts
typescript
import { createClient, GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { betterAuth } from "better-auth";
import { DataModel } from "./_generated/dataModel";
import { components, internal } from "./_generated/api";
import authConfig from "./auth.config";

const siteUrl = process.env.SITE_URL!;

export const authComponent = createClient<DataModel>(components.betterAuth, {
  authFunctions: internal.auth,
  triggers: {
    user: {
      onCreate: async (ctx, authUser) => {
        // Sync to your users table
        const userId = await ctx.db.insert("users", { email: authUser.email });
        await authComponent.setUserId(ctx, authUser._id, userId);
      },
    },
  },
});

export const createAuth = (ctx: GenericCtx<DataModel>) => {
  return betterAuth({
    baseURL: siteUrl,
    database: authComponent.adapter(ctx),
    emailAndPassword: { enabled: true },
    plugins: [convex({ authConfig })],
  });
};

// Auth helpers for queries/mutations
export const { getUser, safeGetUser } = authComponent.authHelpers(createAuth);
convex/http.ts
typescript
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./auth";

const http = httpRouter();
authComponent.registerRoutes(http, createAuth);
export default http;
convex/schema.ts
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    email: v.string(),
    authId: v.optional(v.string()),
  }).index("email", ["email"]),
});
convex/convex.config.ts
typescript
import { defineApp } from "convex/server";
import betterAuth from "@convex-dev/better-auth/convex.config";

const app = defineApp();
app.use(betterAuth);
export default app;
convex/auth.config.ts
typescript
import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
import { AuthConfig } from "@auth/core";

export default {
  providers: [getAuthConfigProvider()],
} satisfies AuthConfig;
convex/auth.ts
typescript
import { createClient, GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { betterAuth } from "better-auth";
import { DataModel } from "./_generated/dataModel";
import { components, internal } from "./_generated/api";
import authConfig from "./auth.config";

const siteUrl = process.env.SITE_URL!;

export const authComponent = createClient<DataModel>(components.betterAuth, {
  authFunctions: internal.auth,
  triggers: {
    user: {
      onCreate: async (ctx, authUser) => {
        // 同步到用户表
        const userId = await ctx.db.insert("users", { email: authUser.email });
        await authComponent.setUserId(ctx, authUser._id, userId);
      },
    },
  },
});

export const createAuth = (ctx: GenericCtx<DataModel>) => {
  return betterAuth({
    baseURL: siteUrl,
    database: authComponent.adapter(ctx),
    emailAndPassword: { enabled: true },
    plugins: [convex({ authConfig })],
  });
};

// 供查询/变更使用的认证工具函数
export const { getUser, safeGetUser } = authComponent.authHelpers(createAuth);
convex/http.ts
typescript
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./auth";

const http = httpRouter();
authComponent.registerRoutes(http, createAuth);
export default http;
convex/schema.ts
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    email: v.string(),
    authId: v.optional(v.string()),
  }).index("email", ["email"]),
});

2. Client Auth Setup

2. 客户端认证配置

src/lib/auth-client.tsx
typescript
import { createAuthClient } from "better-auth/react";
import { convexClient } from "@convex-dev/better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [convexClient()],
});

export const { signIn, signUp, signOut, useSession } = authClient;
src/lib/auth-client.tsx
typescript
import { createAuthClient } from "better-auth/react";
import { convexClient } from "@convex-dev/better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [convexClient()],
});

export const { signIn, signUp, signOut, useSession } = authClient;

3. Server Auth Setup

3. 服务端认证配置

src/lib/auth-server.ts
typescript
import { convexBetterAuthReactStart } from "@convex-dev/better-auth/react-start";

export const {
  handler,
  getToken,
  fetchAuthQuery,
  fetchAuthMutation,
  fetchAuthAction,
} = convexBetterAuthReactStart({
  convexUrl: process.env.VITE_CONVEX_URL!,
  convexSiteUrl: process.env.VITE_CONVEX_SITE_URL!,
});
src/lib/auth-server.ts
typescript
import { convexBetterAuthReactStart } from "@convex-dev/better-auth/react-start";

export const {
  handler,
  getToken,
  fetchAuthQuery,
  fetchAuthMutation,
  fetchAuthAction,
} = convexBetterAuthReactStart({
  convexUrl: process.env.VITE_CONVEX_URL!,
  convexSiteUrl: process.env.VITE_CONVEX_SITE_URL!,
});

3b. Mount Auth Route Handler

3b. 挂载认证路由处理器

src/routes/api/auth/$.ts
typescript
import { handler } from "~/lib/auth-server";

export const { GET, POST } = handler;
src/routes/api/auth/$.ts
typescript
import { handler } from "~/lib/auth-server";

export const { GET, POST } = handler;

4. Root Route with Providers

4. 带提供者的根路由

src/routes/__root.tsx
typescript
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
import { QueryClient } from "@tanstack/react-query";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { getToken } from "~/lib/auth-server";
import { authClient } from "~/lib/auth-client";

const getAuth = createServerFn({ method: "GET" }).handler(async () => {
  return await getToken();
});

export const Route = createRootRouteWithContext<{
  queryClient: QueryClient;
  convexQueryClient: ConvexQueryClient;
}>()({
  beforeLoad: async (ctx) => {
    const token = await getAuth();
    if (token) {
      ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);
    }
    return { isAuthenticated: !!token, token };
  },
  component: RootComponent,
});

function RootComponent() {
  const { convexQueryClient, token } = Route.useRouteContext();
  return (
    <ConvexBetterAuthProvider
      client={convexQueryClient.convexClient}
      authClient={authClient}
      initialToken={token}
    >
      <Outlet />
    </ConvexBetterAuthProvider>
  );
}
src/routes/__root.tsx
typescript
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
import { QueryClient } from "@tanstack/react-query";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { getToken } from "~/lib/auth-server";
import { authClient } from "~/lib/auth-client";

const getAuth = createServerFn({ method: "GET" }).handler(async () => {
  return await getToken();
});

export const Route = createRootRouteWithContext<{
  queryClient: QueryClient;
  convexQueryClient: ConvexQueryClient;
}>()({
  beforeLoad: async (ctx) => {
    const token = await getAuth();
    if (token) {
      ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);
    }
    return { isAuthenticated: !!token, token };
  },
  component: RootComponent,
});

function RootComponent() {
  const { convexQueryClient, token } = Route.useRouteContext();
  return (
    <ConvexBetterAuthProvider
      client={convexQueryClient.convexClient}
      authClient={authClient}
      initialToken={token}
    >
      <Outlet />
    </ConvexBetterAuthProvider>
  );
}

5. Protected Route Guard

5. 受保护路由守卫

src/routes/_authed.tsx
typescript
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_authed")({
  beforeLoad: ({ context }) => {
    if (!context.isAuthenticated) {
      throw redirect({ to: "/sign-in" });
    }
  },
  component: () => <Outlet />,
});
src/routes/_authed.tsx
typescript
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_authed")({
  beforeLoad: ({ context }) => {
    if (!context.isAuthenticated) {
      throw redirect({ to: "/sign-in" });
    }
  },
  component: () => <Outlet />,
});

6. Vite Configuration

6. Vite配置

vite.config.ts
typescript
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";

export default defineConfig({
  plugins: [tanstackStart(), react(), tailwindcss()],
  resolve: {
    alias: { "@": path.resolve(__dirname, "./src") },
  },
  ssr: {
    // Required: Bundle @convex-dev/better-auth during SSR
    noExternal: ["@convex-dev/better-auth"],
  },
});
vite.config.ts
typescript
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";

export default defineConfig({
  plugins: [tanstackStart(), react(), tailwindcss()],
  resolve: {
    alias: { "@": path.resolve(__dirname, "./src") },
  },
  ssr: {
    // 必需:在SSR过程中打包@convex-dev/better-auth
    noExternal: ["@convex-dev/better-auth"],
  },
});

7. Router Configuration

环境变量

src/router.tsx
typescript
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import { QueryClient } from "@tanstack/react-query";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

export function createAppRouter() {
  const queryClient = new QueryClient();
  const convexQueryClient = new ConvexQueryClient(convex, { expectAuth: true });
  queryClient.setDefaultOptions({
    queries: { queryKeyHashFn: convexQueryClient.hashFn() },
  });
  convexQueryClient.connect(queryClient);

  return createRouter({
    routeTree,
    context: { queryClient, convexQueryClient },
    defaultPreload: "intent",
  });
}
.env.local
bash
undefined

Environment Variables

Convex部署信息(由
npx convex dev
设置)

.env.local
bash
undefined
CONVEX_DEPLOYMENT=dev:your-deployment VITE_CONVEX_URL=https://your-deployment.convex.cloud VITE_CONVEX_SITE_URL=https://your-deployment.convex.site

Convex deployment (set by
npx convex dev
)

应用URL

CONVEX_DEPLOYMENT=dev:your-deployment VITE_CONVEX_URL=https://your-deployment.convex.cloud VITE_CONVEX_SITE_URL=https://your-deployment.convex.site
VITE_SITE_URL=http://localhost:3000

**Convex环境变量**(通过CLI设置):
```bash

App URL

生成并设置密钥

VITE_SITE_URL=http://localhost:3000

**Convex environment variables** (set via CLI):
```bash
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)

Generate and set secret

设置站点URL

npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
npx convex env set SITE_URL http://localhost:3000

Set site URL

OAuth(可选)

npx convex env set SITE_URL http://localhost:3000
npx convex env set GITHUB_CLIENT_ID your-id npx convex env set GITHUB_CLIENT_SECRET your-secret
undefined

OAuth (optional)

常见模式

受保护查询

npx convex env set GITHUB_CLIENT_ID your-id npx convex env set GITHUB_CLIENT_SECRET your-secret
undefined
typescript
// convex/todos.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getUser, safeGetUser } from "./auth";

export const get = query({
  handler: async (ctx) => {
    const user = await safeGetUser(ctx);
    if (!user) return [];
    return ctx.db.query("todos").withIndex("userId", (q) => q.eq("userId", user._id)).collect();
  },
});

export const create = mutation({
  args: { text: v.string() },
  handler: async (ctx, args) => {
    const user = await getUser(ctx); // 未认证时抛出错误
    await ctx.db.insert("todos", { text: args.text, userId: user._id, completed: false });
  },
});

Common Patterns

登录组件

Protected Queries

typescript
// convex/todos.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getUser, safeGetUser } from "./auth";

export const get = query({
  handler: async (ctx) => {
    const user = await safeGetUser(ctx);
    if (!user) return [];
    return ctx.db.query("todos").withIndex("userId", (q) => q.eq("userId", user._id)).collect();
  },
});

export const create = mutation({
  args: { text: v.string() },
  handler: async (ctx, args) => {
    const user = await getUser(ctx); // Throws if unauthenticated
    await ctx.db.insert("todos", { text: args.text, userId: user._id, completed: false });
  },
});
typescript
import { authClient } from "~/lib/auth-client";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";

export function SignIn() {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = new FormData(e.currentTarget);
    await authClient.signIn.email({
      email: form.get("email") as string,
      password: form.get("password") as string,
    });
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <Input name="email" type="email" placeholder="邮箱" required />
      <Input name="password" type="password" placeholder="密码" required />
      <Button type="submit">登录</Button>
      <Button type="button" variant="outline" onClick={() => authClient.signIn.social({ provider: "github" })}>
        使用GitHub登录
      </Button>
    </form>
  );
}

Sign In Component

乐观更新

typescript
import { authClient } from "~/lib/auth-client";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";

export function SignIn() {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = new FormData(e.currentTarget);
    await authClient.signIn.email({
      email: form.get("email") as string,
      password: form.get("password") as string,
    });
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <Input name="email" type="email" placeholder="Email" required />
      <Input name="password" type="password" placeholder="Password" required />
      <Button type="submit">Sign In</Button>
      <Button type="button" variant="outline" onClick={() => authClient.signIn.social({ provider: "github" })}>
        Sign in with GitHub
      </Button>
    </form>
  );
}
typescript
import { useMutation } from "@convex-dev/react-query";
import { api } from "convex/_generated/api";

const createTodo = useMutation(api.todos.create).withOptimisticUpdate(
  (localStore, args) => {
    const todos = localStore.getQuery(api.todos.get);
    if (!todos) return;
    localStore.setQuery(api.todos.get, {}, [
      { _id: crypto.randomUUID(), text: args.text, completed: false },
      ...todos,
    ]);
  }
);

Optimistic Updates

认证方式

typescript
import { useMutation } from "@convex-dev/react-query";
import { api } from "convex/_generated/api";

const createTodo = useMutation(api.todos.create).withOptimisticUpdate(
  (localStore, args) => {
    const todos = localStore.getQuery(api.todos.get);
    if (!todos) return;
    localStore.setQuery(api.todos.get, {}, [
      { _id: crypto.randomUUID(), text: args.text, completed: false },
      ...todos,
    ]);
  }
);
Better-Auth支持多种认证方式:
typescript
// 邮箱/密码
authClient.signIn.email({ email, password });
authClient.signUp.email({ email, password, name });

// OAuth
authClient.signIn.social({ provider: "github" });
authClient.signIn.social({ provider: "google" });

// 魔法链接
authClient.signIn.magicLink({ email });

// 登出
authClient.signOut();

// 会话
const { data: session } = authClient.useSession();

Auth Methods

命令

Better-Auth supports multiple authentication methods:
typescript
// Email/Password
authClient.signIn.email({ email, password });
authClient.signUp.email({ email, password, name });

// OAuth
authClient.signIn.social({ provider: "github" });
authClient.signIn.social({ provider: "google" });

// Magic Link
authClient.signIn.magicLink({ email });

// Sign Out
authClient.signOut();

// Session
const { data: session } = authClient.useSession();
bash
undefined

Commands

开发

bash
undefined
bun dev # 启动前端 bunx convex dev # 启动Convex(单独终端)

Development

添加Shadcn组件

bun dev # Start frontend bunx convex dev # Start Convex (separate terminal)
bunx shadcn@latest add button input card form dialog

Add Shadcn components

添加所有Shadcn组件

bunx shadcn@latest add button input card form dialog
bunx shadcn@latest add --all

Add all Shadcn components

构建与部署

bunx shadcn@latest add --all
bun run build bunx convex deploy
undefined

Build & Deploy

结合TanStack Query实现SSR

bun run build bunx convex deploy
undefined
使用
ensureQueryData
useSuspenseQuery
实现SSR:
typescript
// src/routes/_authed/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { convexQuery } from "@convex-dev/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import { api } from "convex/_generated/api";

export const Route = createFileRoute("/_authed/")({
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData(convexQuery(api.todos.get, {}));
  },
  component: Dashboard,
});

function Dashboard() {
  const { data: todos } = useSuspenseQuery(convexQuery(api.todos.get, {}));
  return <TodoList todos={todos} />;
}

SSR with TanStack Query

登出(配合expectAuth: true)

Use
ensureQueryData
and
useSuspenseQuery
for SSR:
typescript
// src/routes/_authed/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { convexQuery } from "@convex-dev/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import { api } from "convex/_generated/api";

export const Route = createFileRoute("/_authed/")({
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData(convexQuery(api.todos.get, {}));
  },
  component: Dashboard,
});

function Dashboard() {
  const { data: todos } = useSuspenseQuery(convexQuery(api.todos.get, {}));
  return <TodoList todos={todos} />;
}
当使用
expectAuth: true
时,登出后需重新加载页面:
typescript
import { authClient } from "~/lib/auth-client";

function SignOutButton() {
  const handleSignOut = async () => {
    await authClient.signOut();
    window.location.reload(); // 使用expectAuth: true时必需
  };
  return <Button onClick={handleSignOut}>登出</Button>;
}

Sign Out (with expectAuth: true)

TanStack Server Functions vs Convex:如何选择

When using
expectAuth: true
, reload the page on sign out:
typescript
import { authClient } from "~/lib/auth-client";

function SignOutButton() {
  const handleSignOut = async () => {
    await authClient.signOut();
    window.location.reload(); // Required with expectAuth: true
  };
  return <Button onClick={handleSignOut}>Sign Out</Button>;
}
使用场景选择方案原因
数据库CRUDConvex查询/变更支持事务、实时更新
实时数据Convex查询自动订阅
第三方API调用Convex动作靠近数据运行
认证处理器TanStack服务端路由处理HTTP Cookie/重定向
SSR预加载TanStack加载器为SSR预获取数据
WebhooksTanStack服务端路由HTTP端点
核心原则: Convex = 数据库与业务逻辑。TanStack Start = HTTP与渲染。
详见
reference/server-functions-vs-convex.md
获取详细指南。

TanStack Server Functions vs Convex: When to Use What

关键集成点

Use CaseUse ThisWhy
Database CRUDConvex query/mutationTransactional, real-time
Real-time dataConvex queryAuto subscriptions
Third-party APIsConvex actionRuns close to data
Auth handlersTanStack server routeHTTP cookies/redirects
SSR preloadingTanStack loaderPre-fetch for SSR
WebhooksTanStack server routeHTTP endpoints
Core principle: Convex = database & business logic. TanStack Start = HTTP & rendering.
See
reference/server-functions-vs-convex.md
for detailed guidelines.
集成项关键文件用途
认证客户端
src/lib/auth-client.tsx
客户端认证方法
认证服务端
src/lib/auth-server.ts
SSR令牌处理
根提供者
src/routes/__root.tsx
认证上下文 + Convex客户端
路由守卫
src/routes/_authed.tsx
重定向未认证用户
后端认证
convex/auth.ts
Better-Auth实例
HTTP路由
convex/http.ts
认证端点注册
数据库
convex/schema.ts
类型安全的schema

Key Integration Points

IntegrationKey FilePurpose
Auth Client
src/lib/auth-client.tsx
Client-side auth methods
Auth Server
src/lib/auth-server.ts
SSR token handling
Root Provider
src/routes/__root.tsx
Auth context + Convex client
Route Guard
src/routes/_authed.tsx
Redirect unauthenticated users
Backend Auth
convex/auth.ts
Better-Auth instance
HTTP Routes
convex/http.ts
Auth endpoint registration
Database
convex/schema.ts
Type-safe schema