tcbs-stack
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTCBS 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
技术栈概述
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | TanStack Start | Full-stack React framework with SSR |
| Backend | Convex | Real-time database & serverless functions |
| Auth | Better-Auth | Flexible authentication with Convex adapter |
| UI | Shadcn UI | Accessible component library |
| 层级 | 技术 | 用途 |
|---|---|---|
| 前端 | TanStack Start | 支持SSR的全栈React框架 |
| 后端 | Convex | 实时数据库与无服务器函数 |
| 认证 | Better-Auth | 支持Convex适配器的灵活认证方案 |
| UI组件 | Shadcn UI | 无障碍组件库 |
Quick Start
快速开始
bash
undefinedbash
undefinedCreate 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 initbun add better-auth @convex-dev/better-auth
**手动安装备选方案:**
```bash
bun create @tanstack/start@latest
bun add tailwindcss @tailwindcss/vite
bunx shadcn@latest initProject 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.tsproject/
├── 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.tsCore 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
undefinedEnvironment Variables
Convex部署信息(由npx convex dev
设置)
npx convex dev.env.local
bash
undefinedCONVEX_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
)
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设置):
```bashApp URL
生成并设置密钥
VITE_SITE_URL=http://localhost:3000
**Convex environment variables** (set via CLI):
```bashnpx 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
undefinedOAuth (optional)
常见模式
—
受保护查询
npx convex env set GITHUB_CLIENT_ID your-id
npx convex env set GITHUB_CLIENT_SECRET your-secret
undefinedtypescript
// 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
undefinedCommands
开发
bash
undefinedbun 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
undefinedBuild & Deploy
结合TanStack Query实现SSR
bun run build
bunx convex deploy
undefined使用和实现SSR:
ensureQueryDatauseSuspenseQuerytypescript
// 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 and for SSR:
ensureQueryDatauseSuspenseQuerytypescript
// 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: truetypescript
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 , reload the page on sign out:
expectAuth: truetypescript
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>;
}| 使用场景 | 选择方案 | 原因 |
|---|---|---|
| 数据库CRUD | Convex查询/变更 | 支持事务、实时更新 |
| 实时数据 | Convex查询 | 自动订阅 |
| 第三方API调用 | Convex动作 | 靠近数据运行 |
| 认证处理器 | TanStack服务端路由 | 处理HTTP Cookie/重定向 |
| SSR预加载 | TanStack加载器 | 为SSR预获取数据 |
| Webhooks | TanStack服务端路由 | HTTP端点 |
核心原则: Convex = 数据库与业务逻辑。TanStack Start = HTTP与渲染。
详见获取详细指南。
reference/server-functions-vs-convex.mdTanStack Server Functions vs Convex: When to Use What
关键集成点
| Use Case | Use This | Why |
|---|---|---|
| Database CRUD | Convex query/mutation | Transactional, real-time |
| Real-time data | Convex query | Auto subscriptions |
| Third-party APIs | Convex action | Runs close to data |
| Auth handlers | TanStack server route | HTTP cookies/redirects |
| SSR preloading | TanStack loader | Pre-fetch for SSR |
| Webhooks | TanStack server route | HTTP endpoints |
Core principle: Convex = database & business logic. TanStack Start = HTTP & rendering.
See for detailed guidelines.
reference/server-functions-vs-convex.md| 集成项 | 关键文件 | 用途 |
|---|---|---|
| 认证客户端 | | 客户端认证方法 |
| 认证服务端 | | SSR令牌处理 |
| 根提供者 | | 认证上下文 + Convex客户端 |
| 路由守卫 | | 重定向未认证用户 |
| 后端认证 | | Better-Auth实例 |
| HTTP路由 | | 认证端点注册 |
| 数据库 | | 类型安全的schema |
Key Integration Points
—
| Integration | Key File | Purpose |
|---|---|---|
| Auth Client | | Client-side auth methods |
| Auth Server | | SSR token handling |
| Root Provider | | Auth context + Convex client |
| Route Guard | | Redirect unauthenticated users |
| Backend Auth | | Better-Auth instance |
| HTTP Routes | | Auth endpoint registration |
| Database | | Type-safe schema |
—