
Better Auth 集成 TanStack Start从虚拟模块报错到tanstackStartCookies的正确姿势【免费下载链接】better-authThe most comprehensive authentication framework项目地址: https://gitcode.com/GitHub_Trending/be/better-authBetter Auth 在将认证能力接入 TanStack Start 时曾因直接导入内部包tanstack/start-server-core而触发 Vite 预打包pre-bundling失败报错Could not resolve #tanstack-router-entry。本文以仓库中的 Postmortem 复盘文档 为核心梳理该问题的根因、正确导入路径、用户侧临时规避方案并结合 官方集成文档 与 插件源码完整演示如何在 React / Solid.js 两种 TanStack Start 应用里配置认证、自动写入 Cookie 并保护路由与 Server Function。读完本文你将能独立规避此类虚拟模块陷阱并正确使用 Better Auth 的 TanStack Start 集成。背景一个反复出现的 Vite 预打包报错问题现象当 Better Auth 的 TanStack Start 集成代码直接执行如下导入时// BAD - 不要这样做 import { setCookie } from tanstack/start-server-core;Vite 在开发服务器启动或依赖预构建阶段会对tanstack/start-server-core执行预打包optimizeDeps而该包内部依赖虚拟模块virtual modules#tanstack-router-entry这类模块无法被常规的依赖扫描与打包流程解析最终抛出错误Could not resolve #tanstack-router-entry复发历史为什么问题会反复出现复盘文档记录了该问题的多次复发均由“同时支持 Solid 与 React 两种 TanStack Start 版本”的 PR 引入PR #6045PR #6235PR #7340对应 v1.4.12 → v1.4.13 版本区间其中 v1.4.14 的首次修复尝试采用了独立的tanstack/react-start-server与tanstack/solid-start-server包但并未解决问题这为后续找到正确方案提供了关键教训解决路径不在“换一个包名”而在于“换一个子路径”。根因内部包的 Vite 配置与虚拟模块tanstack/start-server-core是 TanStack 的内部包它自带特殊的 Vite 配置将诸如#tanstack-router-entry这样的虚拟模块声明为外部依赖external。虚拟模块在构建期由 Vite 插件按需生成无法被当作普通 npm 依赖预先打包。因此一旦 Better Auth 集成模块或任何第三方代码直接 import 该包依赖预构建便会在解析虚拟模块时失败。从当前仓库源码看Better Auth 的 TanStack Start Cookie 插件会动态导入框架专属的 server 子路径而不再触碰内部包React 版本tanstack-start.ts 中await import(tanstack/react-start/server)Solid.js 版本tanstack-start-solid.ts 中await import(tanstack/solid-start/server)这种“延迟动态导入”还带来一个额外好处插件只在真正需要写入 Cookie即认证响应产生set-cookie头时才加载对应框架的 server 模块避免了在模块加载阶段就触发依赖解析。解决方案使用框架专属的/server子路径正确做法是从主包的/server子路径导入该子路径会完整地重新导出re-exporttanstack/start-server-core中的所有 API// GOOD - 适用于 React import { setCookie } from tanstack/react-start/server; // GOOD - 适用于 Solid.js import { setCookie } from tanstack/solid-start/server;注意独立的tanstack/react-start-server与tanstack/solid-start-server包并不能修复此问题。必须使用主包tanstack/react-start/tanstack/solid-start的/server子路径。这一点在当前仓库的依赖声明中也能印证packages/better-auth/package.json 的 peerDependencies 与 devDependencies 中声明的是tanstack/react-start^1.168.4与tanstack/solid-start^1.168.4二者均标记为可选 peer 依赖而不是*-start-server系列包。用户侧临时规避方案如果你在官方修复发布前就遇到了Could not resolve #tanstack-router-entry可以在项目的vite.config.ts中将该内部包从依赖预构建中排除optimizeDeps: { exclude: [tanstack/start-server-core], },排除后 Vite 不再尝试预打包该包虚拟模块的解析交由 TanStack Start 自身的插件链路处理从而绕过报错。这是纯用户侧配置无需修改任何依赖。完整接入React 版 TanStack Start1. 挂载认证处理器创建/src/routes/api/auth/$.ts将 Better Auth 的auth.handler挂载到 TanStack 的 API 路由上import { auth } from /lib/auth import { createFileRoute } from tanstack/react-router export const Route createFileRoute(/api/auth/$)({ server: { handlers: { GET: async ({ request }:{ request: Request }) { return await auth.handler(request) }, POST: async ({ request }:{ request: Request }) { return await auth.handler(request) }, }, }, })2. 配置tanstackStartCookies插件在src/lib/auth.ts中引入 Cookie 插件。文档明确要求确保它位于 plugins 数组的最后一个。插件源码中通过warnIfCookiePluginNotLast见 cookie-plugin-guard.ts在运行时检测插件顺序若顺序不当会发出警告——因为后续的after钩子需要先于它处理响应头才能让 Cookie 写入逻辑拿到完整的set-cookie信息。import { betterAuth } from better-auth; import { tanstackStartCookies } from better-auth/tanstack-start; export const auth betterAuth({ //...your config plugins: [tanstackStartCookies()] // make sure this is the last plugin in the array })React 版插件内部逻辑tanstack-start.ts为通过after钩子拦截每个认证请求的响应从ctx.context.responseHeaders中读取set-cookie头用parseSetCookieHeader解析出全部 Cookie 键值动态导入tanstack/react-start/server的setCookie配合toCookieOptions还原 Cookie 属性后逐个写入若当前上下文标记为router或写入发生在 Server Component 场景而抛错则静默跳过。3. 调用认证 APICookie 自动落盘之后调用auth.api中需要写 Cookie 的方法如signInEmailCookie 会被自动通过 TanStack Start 的 Cookie 系统写入import { auth } from /lib/auth const signIn async () { await auth.api.signInEmail({ body: { email: useremail.com, password: password, } }) }完整接入Solid.js 版 TanStack StartSolid.js 应用流程完全相同区别只在于插件入口与导入来源import { betterAuth } from better-auth; import { tanstackStartCookies } from better-auth/tanstack-start/solid; export const auth betterAuth({ //...your config plugins: [tanstackStartCookies()] // make sure this is the last plugin in the array })对应地Solid.js 版插件tanstack-start-solid.ts动态导入的是tanstack/solid-start/server的setCookie其余解析与写入逻辑与 React 版一致插件 id 为tanstack-start-cookies-solid。两个入口在 packages/better-auth/package.json 的exports中分别映射为better-auth/tanstack-start→./src/integrations/tanstack-start.tsbetter-auth/tanstack-start/solid→./src/integrations/tanstack-start-solid.ts两者同时被登记在 tsdown.config.ts 的构建入口中产出对应的 ESM 产物。保护受认证资源创建服务端会话辅助函数使用createServerFn与getRequestHeaders来自tanstack/react-start/server即上文所述的/server子路径读取请求头并校验会话import { createServerFn } from tanstack/react-start; import { getRequestHeaders } from tanstack/react-start/server; import { auth } from /lib/auth; export const getSession createServerFn({ method: GET }).handler(async () { const headers getRequestHeaders(); const session await auth.api.getSession({ headers }); return session; }); export const ensureSession createServerFn({ method: GET }).handler(async () { const headers getRequestHeaders(); const session await auth.api.getSession({ headers }); if (!session) { throw new Error(Unauthorized); } return session; });保护单个路由在路由定义中使用beforeLoad确保每次导航包括Link触发的客户端导航都会校验会话import { createFileRoute, redirect } from tanstack/react-router import { getSession } from /lib/auth.functions export const Route createFileRoute(/dashboard)({ beforeLoad: async () { const session await getSession(); if (!session) { throw redirect({ to: /login }); } return { user: session.user }; }, component: Dashboard, }) function Dashboard() { const { user } Route.useRouteContext(); return divWelcome, {user.name}!/div }用无路径 Layout 路由保护多个页面先创建无路径布局路由_protected.tsx未登录时重定向并携带当前地址作为回跳参数import { createFileRoute, redirect, Outlet } from tanstack/react-router import { getSession } from /lib/auth.functions export const Route createFileRoute(/_protected)({ beforeLoad: async ({ location }) { const session await getSession(); if (!session) { throw redirect({ to: /login, search: { redirect: location.href }, }); } return { user: session.user }; }, component: () Outlet /, })再把需要保护的页面dashboard.tsx、settings.tsx等嵌套进_protected目录即可统一受控src/routes/ ├── _protected.tsx # 无路径布局统一鉴权 ├── _protected/ │ ├── dashboard.tsx │ └── settings.tsx └── login.tsx保护 Server Function借助ensureSession在 Server Function 内直接抛错即可阻断未授权调用import { createServerFn } from tanstack/react-start; import { ensureSession } from ./auth.functions; export const createPost createServerFn({ method: POST }) .inputValidator((data: { title: string }) data) .handler(async ({ data }) { const session await ensureSession(); const post await db.posts.create({ title: data.title, authorId: session.user.id, }); return post; });经验教训与自查清单复盘文档给出四条核心教训也即接入 TanStack Start 时应当遵守的规则绝不直接导入tanstack/start-server-core——它是带虚拟模块的内部包直接导入会触发Could not resolve #tanstack-router-entry。始终使用框架专属子路径——React 用tanstack/react-start/serverSolid.js 用tanstack/solid-start/server。不要使用独立的-server包——tanstack/react-start-server无法修复该问题。两者的 API 是同一套——setCookie、getCookie等能力在两个子路径下均可获得代码迁移只改导入来源即可。官方修复涉及的文件均可在当前仓库查看packages/better-auth/src/integrations/tanstack-start.ts —— React 版导入改为tanstack/react-start/serverpackages/better-auth/src/integrations/tanstack-start-solid.ts —— Solid.js 版导入改为tanstack/solid-start/serverpackages/better-auth/package.json —— peerDependencies 由tanstack/*-start-server更新为tanstack/*-start^1.0.0可选完整的 TanStack Start 集成说明还可参考 官方集成文档含npm create tanstack/start脚手架一键集成 Better Auth 的用法与 安装指南。若你仍在使用旧版本并在升级前遇到该报错optimizeDeps.exclude是可靠的过渡手段。【免费下载链接】better-authThe most comprehensive authentication framework项目地址: https://gitcode.com/GitHub_Trending/be/better-auth创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考