-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
examples: add tanstack start with better auth example #6352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdds a complete new example React + TanStack Start project demonstrating GitHub OAuth integration via Better Auth, including routing architecture, protected routes, session management, and environment configuration. No modifications to existing codebases; purely additive example scaffolding. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In @examples/react/start-basic-better-auth/package.json:
- Around line 1-32: Replace the pinned versions for the internal TanStack
packages so the example uses workspace packages: change the dependency entries
for "@tanstack/react-router", "@tanstack/react-router-devtools", and
"@tanstack/react-start" from their current "^1.147.0" values to "workspace:*" so
the example builds against the monorepo workspace versions.
In @examples/react/start-basic-better-auth/src/components/NotFound.tsx:
- Line 3: The children prop in the NotFound component is typed as any; update
the signature in export function NotFound({ children }: { children?: any }) to
use React.ReactNode instead (e.g., { children?: React.ReactNode }) and add the
appropriate import (either import React from 'react' or import type { ReactNode
} from 'react' and use ReactNode) so the file compiles under TypeScript strict
mode; keep the change scoped to the NotFound component signature and imports.
In @examples/react/start-basic-better-auth/src/routes/__root.tsx:
- Around line 81-85: The sign-out flow in handleSignOut lacks error handling;
wrap the async sequence (authClient.signOut, router.invalidate, router.navigate)
in a try/catch/finally: attempt signOut and only navigate on success, catch and
log the error (and surface user feedback via UI toast or set an error state) if
signOut fails, and call router.invalidate in finally to ensure client state is
refreshed regardless of outcome; reference handleSignOut, authClient.signOut,
router.invalidate, and router.navigate when making these changes.
In @examples/react/start-basic-better-auth/src/routes/protected.tsx:
- Around line 47-52: The code renders the entire session object via
JSON.stringify(session) which can leak sensitive tokens; update the JSX to avoid
printing full session: either gate the debug block behind a development-only
flag (e.g., process.env.NODE_ENV === "development") or replace
JSON.stringify(session, null, 2) with a redacted subset such as JSON.stringify({
user: { name: session?.user?.name, email: session?.user?.email } }, null, 2);
ensure you still reference the same session variable and keep the debug wrapper
conditional so it never shows in production.
In @examples/react/start-basic-better-auth/src/utils/auth.ts:
- Around line 10-11: Replace the non-null assertions for
process.env.GITHUB_CLIENT_ID and process.env.GITHUB_CLIENT_SECRET with a runtime
check that validates these env vars at startup: read
process.env.GITHUB_CLIENT_ID and process.env.GITHUB_CLIENT_SECRET into
variables, if either is missing throw or log a clear error (e.g.,
"GITHUB_CLIENT_ID is required") and exit, then use those validated variables for
clientId and clientSecret; reference the clientId/clientSecret fields in the
exported auth config (or the function that builds it) so the values are
guaranteed present.
- Around line 7-25: The Better Auth config in the exported auth (created via
betterAuth) is missing baseURL and trustedOrigins which breaks OAuth/CSRF in
production; update the betterAuth call for auth to include baseURL:
process.env.BETTER_AUTH_URL and trustedOrigins: [process.env.BETTER_AUTH_URL]
(or an array of allowed origins including localhost/dev and production URLs)
alongside the existing socialProviders and plugins, and then add the
corresponding BETTER_AUTH_URL (and any additional allowed origins) to your
environment files/.env.example.
🧹 Nitpick comments (11)
examples/react/start-basic-better-auth/.gitignore (1)
1-6: LGTM; consider widening env ignores to.env.*(while keeping.env.example).
Current rules are fine; if you want to prevent accidental commits of.env.development/.env.production, consider adding.env.*and a negation for.env.example.examples/react/start-basic-better-auth/tsconfig.json (1)
1-24: Strict TS config looks good; consider addingforceConsistentCasingInFileNames.
This helps avoid case-only import bugs that show up on CI/Linux but not on macOS/Windows.Proposed diff
{ "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "~/*": ["./src/*"] } }, "include": ["src", "env.d.ts", "vite.config.ts"] }examples/react/start-basic-better-auth/vite.config.ts (1)
9-10: Consider removingstrictPortfor better developer experience.Setting
strictPort: truewill cause the dev server to fail immediately if port 10000 is already in use. For an example project, this might be overly strict and could frustrate developers who have other services running on that port. Consider removing this option or setting it tofalseto allow Vite to find an available port automatically.♻️ More flexible configuration
server: { port: 10000, - strictPort: true, },examples/react/start-basic-better-auth/src/routes/__root.tsx (1)
100-105: Optional chaining is redundant within the conditional block.Since
routeContext.sessionis already verified as truthy on line 100, the optional chaining onrouteContext.session?.user?.nameandrouteContext.session?.user?.emailis redundant. While this doesn't cause issues, simplifying torouteContext.session.user?.nameimproves code clarity.♻️ Simplify optional chaining
{routeContext.session ? ( <> <span className="text-gray-600"> - {routeContext.session?.user?.name || - routeContext.session?.user?.email} + {routeContext.session.user?.name || + routeContext.session.user?.email} </span>examples/react/start-basic-better-auth/.env.example (1)
1-3: Consider adding secret generation instructions.The comment mentions the minimum length requirement, but developers might benefit from guidance on how to generate a secure secret. Consider adding a comment with a command they can run.
📝 Enhanced documentation
# Better Auth Configuration -BETTER_AUTH_SECRET=your-secret-key-here-min-32-chars-long +# Generate a secure secret with: openssl rand -base64 32 +BETTER_AUTH_SECRET=your-secret-key-here-min-32-chars-longexamples/react/start-basic-better-auth/src/router.tsx (1)
11-12: Use consistent component reference pattern.The
defaultErrorComponentuses a direct component reference whiledefaultNotFoundComponentwraps the component in an inline arrow function. This inconsistency creates a new component instance on each evaluation and could interfere with hot module replacement during development.♻️ Make component references consistent
defaultPreload: "intent", defaultErrorComponent: DefaultCatchBoundary, - defaultNotFoundComponent: () => <NotFound />, + defaultNotFoundComponent: NotFound, scrollRestoration: true,examples/react/start-basic-better-auth/src/routes/login.tsx (1)
14-19: Consider error handling for authentication failures.The
authClient.signIn.social()call has no error handling. If the OAuth flow fails (network error, user denies permission, etc.), the user receives no feedback.♻️ Recommended enhancement with error handling
function Login() { + const [error, setError] = React.useState<string | null>(null); + const handleGitHubSignIn = () => { - authClient.signIn.social({ - provider: "github", - callbackURL: "/", - }); + setError(null); + authClient.signIn.social({ + provider: "github", + callbackURL: "/", + }).catch((err) => { + setError("Failed to sign in. Please try again."); + console.error("Sign-in error:", err); + }); }; return ( <div className="max-w-md mx-auto mt-10"> <h1 className="text-2xl font-bold mb-6 text-center">Sign In</h1> + {error && ( + <div className="mb-4 p-3 bg-red-100 text-red-700 rounded"> + {error} + </div> + )} + <div className="space-y-4">examples/react/start-basic-better-auth/src/routes/index.tsx (1)
27-33: Validate image URLs to prevent potential security risks.The user avatar is rendered directly from
user.imagewithout validation. While GitHub OAuth typically returns trusted URLs, it's a good practice to validate image sources to prevent potential SSRF or content injection if the OAuth provider's data is compromised or the provider is changed.Consider adding URL validation or using a Content Security Policy to restrict image sources to trusted domains (e.g., GitHub's CDN).
examples/react/start-basic-better-auth/src/routes/protected.tsx (2)
3-10: Preserve “return to” intent on auth redirect (better UX).
Right now unauthenticated users always land on/loginwith no way to return to/protectedafter signing in; consider passing asearchparam (e.g.next=/protected) and consuming it in the login flow.Proposed change
export const Route = createFileRoute("/protected")({ beforeLoad: ({ context }) => { if (!context.session) { - throw redirect({ to: "/login" }); + throw redirect({ + to: "/login", + search: { next: "/protected" }, + }); } }, component: Protected, });
33-41: Harden avatar rendering (privacy/perf).
Consider addingloading="lazy",referrerPolicy="no-referrer"(common for 3rd-party avatars), and explicitwidth/heightto reduce layout shift.examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx (1)
39-48: Optional: make “Go Back” more robust when history is empty.
window.history.back()can no-op on first-entry pages; consider falling back torouter.navigate({ to: '/' })(or just let the Link navigate) whenhistory.length <= 1.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
examples/react/start-basic-better-auth/.env.exampleexamples/react/start-basic-better-auth/.gitignoreexamples/react/start-basic-better-auth/README.mdexamples/react/start-basic-better-auth/package.jsonexamples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsxexamples/react/start-basic-better-auth/src/components/NotFound.tsxexamples/react/start-basic-better-auth/src/routeTree.gen.tsexamples/react/start-basic-better-auth/src/router.tsxexamples/react/start-basic-better-auth/src/routes/__root.tsxexamples/react/start-basic-better-auth/src/routes/api/auth/$.tsexamples/react/start-basic-better-auth/src/routes/index.tsxexamples/react/start-basic-better-auth/src/routes/login.tsxexamples/react/start-basic-better-auth/src/routes/protected.tsxexamples/react/start-basic-better-auth/src/styles/app.cssexamples/react/start-basic-better-auth/src/utils/auth-client.tsexamples/react/start-basic-better-auth/src/utils/auth.tsexamples/react/start-basic-better-auth/tsconfig.jsonexamples/react/start-basic-better-auth/vite.config.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety for all code
Files:
examples/react/start-basic-better-auth/src/router.tsxexamples/react/start-basic-better-auth/src/routes/api/auth/$.tsexamples/react/start-basic-better-auth/src/routes/index.tsxexamples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsxexamples/react/start-basic-better-auth/src/utils/auth.tsexamples/react/start-basic-better-auth/src/routes/__root.tsxexamples/react/start-basic-better-auth/src/components/NotFound.tsxexamples/react/start-basic-better-auth/src/utils/auth-client.tsexamples/react/start-basic-better-auth/vite.config.tsexamples/react/start-basic-better-auth/src/routes/protected.tsxexamples/react/start-basic-better-auth/src/routeTree.gen.tsexamples/react/start-basic-better-auth/src/routes/login.tsx
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Implement ESLint rules for router best practices using the ESLint plugin router
Files:
examples/react/start-basic-better-auth/src/router.tsxexamples/react/start-basic-better-auth/src/routes/api/auth/$.tsexamples/react/start-basic-better-auth/src/routes/index.tsxexamples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsxexamples/react/start-basic-better-auth/src/utils/auth.tsexamples/react/start-basic-better-auth/src/routes/__root.tsxexamples/react/start-basic-better-auth/src/components/NotFound.tsxexamples/react/start-basic-better-auth/src/utils/auth-client.tsexamples/react/start-basic-better-auth/vite.config.tsexamples/react/start-basic-better-auth/src/routes/protected.tsxexamples/react/start-basic-better-auth/src/routeTree.gen.tsexamples/react/start-basic-better-auth/src/routes/login.tsx
**/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Use workspace protocol
workspace:*for internal dependencies in package.json files
Files:
examples/react/start-basic-better-auth/package.json
🧠 Learnings (9)
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router
Applied to files:
examples/react/start-basic-better-auth/src/router.tsxexamples/react/start-basic-better-auth/src/routes/api/auth/$.tsexamples/react/start-basic-better-auth/src/routes/index.tsxexamples/react/start-basic-better-auth/src/routes/__root.tsxexamples/react/start-basic-better-auth/src/routeTree.gen.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
Applied to files:
examples/react/start-basic-better-auth/src/router.tsxexamples/react/start-basic-better-auth/src/routes/protected.tsxexamples/react/start-basic-better-auth/src/routeTree.gen.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with extensive type safety for all code
Applied to files:
examples/react/start-basic-better-auth/tsconfig.json
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.
Applied to files:
examples/react/start-basic-better-auth/tsconfig.jsonexamples/react/start-basic-better-auth/src/routeTree.gen.tsexamples/react/start-basic-better-auth/package.json
📚 Learning: 2025-12-25T13:04:55.492Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
Applied to files:
examples/react/start-basic-better-auth/.gitignoreexamples/react/start-basic-better-auth/package.json
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.
Applied to files:
examples/react/start-basic-better-auth/.gitignoreexamples/react/start-basic-better-auth/src/routes/protected.tsxexamples/react/start-basic-better-auth/src/routeTree.gen.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.
Applied to files:
examples/react/start-basic-better-auth/src/routeTree.gen.ts
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.
Applied to files:
examples/react/start-basic-better-auth/src/routeTree.gen.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions
Applied to files:
examples/react/start-basic-better-auth/src/routeTree.gen.ts
🧬 Code graph analysis (6)
examples/react/start-basic-better-auth/src/router.tsx (3)
examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx (1)
DefaultCatchBoundary(10-53)examples/react/start-basic-better-auth/src/components/NotFound.tsx (1)
NotFound(3-25)examples/react/start-basic-better-auth/src/routes/__root.tsx (1)
RouterContext(18-20)
examples/react/start-basic-better-auth/src/routes/api/auth/$.ts (5)
examples/react/start-basic-better-auth/src/routes/__root.tsx (1)
Route(28-51)examples/react/start-basic-better-auth/src/routes/index.tsx (1)
Route(3-5)examples/react/start-basic-better-auth/src/routes/login.tsx (1)
Route(4-11)examples/react/start-basic-better-auth/src/routes/protected.tsx (1)
Route(3-10)examples/react/start-basic-better-auth/src/utils/auth.ts (1)
auth(7-25)
examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx (3)
e2e/react-start/server-routes/src/components/DefaultCatchBoundary.tsx (1)
DefaultCatchBoundary(10-53)examples/react/start-basic-cloudflare/src/components/DefaultCatchBoundary.tsx (1)
DefaultCatchBoundary(10-53)examples/solid/start-basic-authjs/src/components/DefaultCatchBoundary.tsx (1)
DefaultCatchBoundary(10-53)
examples/react/start-basic-better-auth/src/routes/__root.tsx (7)
examples/react/start-basic-better-auth/src/utils/auth.ts (2)
AuthSession(28-28)auth(7-25)packages/start-server-core/src/request-response.ts (1)
getRequestHeaders(149-152)examples/react/start-basic-better-auth/src/routes/api/auth/$.ts (1)
Route(8-15)examples/react/start-basic-better-auth/src/routes/index.tsx (1)
Route(3-5)examples/react/start-basic-better-auth/src/routes/login.tsx (1)
Route(4-11)examples/react/start-basic-better-auth/src/routes/protected.tsx (1)
Route(3-10)examples/react/start-basic-better-auth/src/utils/auth-client.ts (1)
authClient(3-3)
examples/react/start-basic-better-auth/src/routeTree.gen.ts (1)
examples/react/start-basic-better-auth/src/router.tsx (1)
getRouter(7-19)
examples/react/start-basic-better-auth/src/routes/login.tsx (5)
examples/react/start-basic-better-auth/src/routes/__root.tsx (1)
Route(28-51)examples/react/start-basic-better-auth/src/routes/index.tsx (1)
Route(3-5)examples/react/start-basic-better-auth/src/routes/protected.tsx (1)
Route(3-10)examples/react/start-basic-better-auth/src/utils/auth-client.ts (1)
authClient(3-3)packages/router-core/src/route.ts (1)
path(1590-1592)
🔇 Additional comments (6)
examples/react/start-basic-better-auth/src/styles/app.css (1)
1-1: The@import "tailwindcss";syntax is correct for Tailwind v4.1.18, which is the version specified in this project's package.json. No changes needed.examples/react/start-basic-better-auth/src/utils/auth-client.ts (1)
1-3: No action required—createAuthClient()defaults are correct for this TanStack Start setup. The client is used only in browser context (onClick handlers, client components), and the server uses a separateauthinstance. The defaultbasePathof/api/authmatches your route configuration atroutes/api/auth/$.ts, so explicit configuration is unnecessary.examples/react/start-basic-better-auth/src/routes/__root.tsx (1)
22-26: LGTM! Clean server function implementation.The session fetching logic correctly uses the server-side headers and Better Auth API. Errors naturally propagate to route error boundaries, which is the expected behavior in TanStack Router.
examples/react/start-basic-better-auth/src/routes/api/auth/$.ts (1)
8-15: LGTM! Standard Better Auth API route pattern.The implementation correctly delegates all authentication requests to Better Auth's handler. This is the standard integration pattern and properly handles all auth-related endpoints under
/api/auth/*.examples/react/start-basic-better-auth/src/router.tsx (1)
14-16: LGTM! Type-safe context initialization.The use of
satisfies RouterContextensures type safety while allowing TypeScript to infer the exact type. The initialsession: nullvalue will be properly populated by the root route'sbeforeLoadhook.examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx (1)
1-53: Matches existing TanStack Start examples; looks good.
Implementation aligns with the repo’s otherDefaultCatchBoundarypatterns (router invalidate + root-aware navigation).
| { | ||
| "name": "tanstack-start-example-basic-better-auth", | ||
| "private": true, | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite dev", | ||
| "build": "vite build", | ||
| "preview": "vite preview", | ||
| "start": "pnpx srvx --prod -s ../client dist/server/server.js" | ||
| }, | ||
| "dependencies": { | ||
| "@tanstack/react-router": "^1.147.0", | ||
| "@tanstack/react-router-devtools": "^1.147.0", | ||
| "@tanstack/react-start": "^1.147.0", | ||
| "better-auth": "^1.4.10", | ||
| "react": "^19.0.0", | ||
| "react-dom": "^19.0.0", | ||
| "tailwind-merge": "^2.6.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@tailwindcss/vite": "^4.1.18", | ||
| "@types/node": "^22.5.4", | ||
| "@types/react": "^19.0.8", | ||
| "@types/react-dom": "^19.0.3", | ||
| "@vitejs/plugin-react": "^4.6.0", | ||
| "tailwindcss": "^4.1.18", | ||
| "typescript": "^5.7.2", | ||
| "vite": "^7.1.7", | ||
| "vite-tsconfig-paths": "^5.1.4" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use workspace:* for the internal @tanstack/* dependencies (per repo guidelines).
This example lives inside the TanStack monorepo, so @tanstack/react-router, @tanstack/react-router-devtools, and @tanstack/react-start should be workspace:* rather than pinned ^1.147.0, to ensure the example always tests against the PR’s actual workspace packages.
Proposed diff
"dependencies": {
- "@tanstack/react-router": "^1.147.0",
- "@tanstack/react-router-devtools": "^1.147.0",
- "@tanstack/react-start": "^1.147.0",
+ "@tanstack/react-router": "workspace:*",
+ "@tanstack/react-router-devtools": "workspace:*",
+ "@tanstack/react-start": "workspace:*",
"better-auth": "^1.4.10",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^2.6.0"
},In TanStack/router examples, are @tanstack/react-router and @tanstack/react-start expected to be referenced as "workspace:*" in package.json?
🤖 Prompt for AI Agents
In @examples/react/start-basic-better-auth/package.json around lines 1 - 32,
Replace the pinned versions for the internal TanStack packages so the example
uses workspace packages: change the dependency entries for
"@tanstack/react-router", "@tanstack/react-router-devtools", and
"@tanstack/react-start" from their current "^1.147.0" values to "workspace:*" so
the example builds against the monorepo workspace versions.
| @@ -0,0 +1,25 @@ | |||
| import { Link } from "@tanstack/react-router"; | |||
|
|
|||
| export function NotFound({ children }: { children?: any }) { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Replace any type with ReactNode for strict TypeScript compliance.
The children prop is typed as any, which violates TypeScript strict mode requirements. Use React.ReactNode for proper type safety.
As per coding guidelines, TypeScript strict mode with extensive type safety is required for all .tsx files.
♻️ Proposed fix
-export function NotFound({ children }: { children?: any }) {
+export function NotFound({ children }: { children?: React.ReactNode }) {
return (📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function NotFound({ children }: { children?: any }) { | |
| export function NotFound({ children }: { children?: React.ReactNode }) { |
🤖 Prompt for AI Agents
In @examples/react/start-basic-better-auth/src/components/NotFound.tsx at line
3, The children prop in the NotFound component is typed as any; update the
signature in export function NotFound({ children }: { children?: any }) to use
React.ReactNode instead (e.g., { children?: React.ReactNode }) and add the
appropriate import (either import React from 'react' or import type { ReactNode
} from 'react' and use ReactNode) so the file compiles under TypeScript strict
mode; keep the change scoped to the NotFound component signature and imports.
| const handleSignOut = async () => { | ||
| await authClient.signOut(); | ||
| await router.invalidate(); | ||
| await router.navigate({ to: "/" }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling to the sign-out flow.
The handleSignOut function performs multiple async operations that could fail (network issues, session errors), but doesn't handle potential errors. If any operation fails, the user receives no feedback and the app might be left in an inconsistent state (e.g., client thinks user is signed out but server session persists).
🛡️ Proposed fix with error handling
const handleSignOut = async () => {
- await authClient.signOut();
- await router.invalidate();
- await router.navigate({ to: "/" });
+ try {
+ await authClient.signOut();
+ await router.invalidate();
+ await router.navigate({ to: "/" });
+ } catch (error) {
+ console.error("Sign out failed:", error);
+ // Consider showing a user-friendly error message
+ }
};🤖 Prompt for AI Agents
In @examples/react/start-basic-better-auth/src/routes/__root.tsx around lines 81
- 85, The sign-out flow in handleSignOut lacks error handling; wrap the async
sequence (authClient.signOut, router.invalidate, router.navigate) in a
try/catch/finally: attempt signOut and only navigate on success, catch and log
the error (and surface user feedback via UI toast or set an error state) if
signOut fails, and call router.invalidate in finally to ensure client state is
refreshed regardless of outcome; reference handleSignOut, authClient.signOut,
router.invalidate, and router.navigate when making these changes.
| <div className="mt-8 p-4 bg-gray-100 rounded-lg"> | ||
| <h3 className="font-semibold mb-2">Session Data (Debug)</h3> | ||
| <pre className="text-xs overflow-auto bg-gray-800 text-green-400 p-4 rounded"> | ||
| {JSON.stringify(session, null, 2)} | ||
| </pre> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid rendering full session objects in the UI (security/privacy footgun).
JSON.stringify(session) can easily expose sensitive fields (tokens/IDs) if the session shape changes; gate it behind a dev-only flag or redact to a safe subset (e.g., user: { name, email }).
🤖 Prompt for AI Agents
In @examples/react/start-basic-better-auth/src/routes/protected.tsx around lines
47 - 52, The code renders the entire session object via JSON.stringify(session)
which can leak sensitive tokens; update the JSX to avoid printing full session:
either gate the debug block behind a development-only flag (e.g.,
process.env.NODE_ENV === "development") or replace JSON.stringify(session, null,
2) with a redacted subset such as JSON.stringify({ user: { name:
session?.user?.name, email: session?.user?.email } }, null, 2); ensure you still
reference the same session variable and keep the debug wrapper conditional so it
never shows in production.
| export const auth = betterAuth({ | ||
| socialProviders: { | ||
| github: { | ||
| clientId: process.env.GITHUB_CLIENT_ID!, | ||
| clientSecret: process.env.GITHUB_CLIENT_SECRET!, | ||
| }, | ||
| }, | ||
| plugins: [ | ||
| // Must be last plugin in the array | ||
| tanstackStartCookies(), | ||
| ], | ||
| // Stateless sessions (no database required) | ||
| session: { | ||
| cookieCache: { | ||
| enabled: true, | ||
| maxAge: 60 * 60 * 24 * 7, | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Better Auth baseURL trustedOrigins configuration OAuth GitHub
💡 Result:
Key points and recommended config for Better Auth + GitHub OAuth
-
Always set baseURL explicitly (or BETTER_AUTH_URL). Do not rely on request inference — set the full origin your server answers on (include https when applicable). [1][2]
-
trustedOrigins must include every origin that will call Better Auth endpoints (frontend origin(s), any proxy/origin used during OAuth redirects, and app URL schemes for mobile). Use exact origins or supported wildcards for subdomains. Examples:
- Web: "https://app.example.com", "https://example.com"
- Local dev: "http://localhost:3000"
- Mobile/custom-scheme: "bac://my.app" (or "bac://" for wildcard mobile schemes). [0][2][3]
-
GitHub OAuth redirect URI(s) must match what you register in GitHub and what Better Auth uses (set redirectURI in the provider config). Ensure the origin of the redirect is listed in trustedOrigins. Use full callback path (eg. https://app.example.com/api/auth/callback/github). [1][8]
-
Cookies & security:
- Better Auth will mark cookies secure when baseURL is https — confirm baseURL uses https in production or set useSecureCookies if forcing. Cross-subdomain cookie sharing requires crossSubDomainCookies domain (e.g. ".example.com"). [0][1]
- Wildcards & protocol nuance:
- Better Auth supports wildcard trustedOrigins (e.g. ".example.com" or "https://.example.com") but be careful with protocol-specific matching — test that HTTP vs HTTPS behaviors match your needs. Some reported issues exist with protocol-specific wildcard matching; prefer explicit origins when possible. [6][7]
- Docker / proxy notes:
- When running in containers or behind proxies, ensure the origin header seen by Better Auth matches an entry in trustedOrigins (use the public origin the browser uses, not internal container hostnames). Mismatched origins cause CORS/CSRF rejections. [3]
Minimal example configuration
- Server-side (better-auth config):
- baseURL: "https://app.example.com" (or set BETTER_AUTH_URL)
- trustedOrigins: ["https://app.example.com","https://example.com","http://localhost:3000","bac://my.app"]
- socialProviders.github.redirectURI: "https://app.example.com/api/auth/callback/github"
- advanced.crossSubDomainCookies.domain: ".example.com" (if sharing cookies across subdomains)
- advanced.useSecureCookies: true (optional force)
References
- Better Auth docs: baseURL, basePath, trustedOrigins, CSRF/cookie behavior. [1][2]
- Better Auth docs (canary/beta pages and client docs) — examples for mobile/custom-scheme and trustedOrigins usage. [0][3]
- GitHub issue threads on docker/proxy origin mismatches and wildcard/protocol behavior. [3][7]
Sources
[0] better-auth.instructions gist (docs excerpts).
[1] Better Auth — Options / reference (stable).
[2] Better Auth — Options (beta/canary docs on trustedOrigins).
[3] better-auth GitHub issue discussing docker/origin/CORS mismatches.
[7] Issue on protocol-specific wildcard trusted origins not working.
🏁 Script executed:
# Find all Better Auth related files and configurations
find . -type f -name "*.ts" -o -name "*.tsx" -o -name "*.json" | xargs grep -l "betterAuth\|better-auth" | head -20Repository: TanStack/router
Length of output: 778
🏁 Script executed:
# Check for package.json to see Better Auth version
find . -name "package.json" -type f | xargs cat | grep -A 2 -B 2 "better-auth"Repository: TanStack/router
Length of output: 763
🏁 Script executed:
# Look for other auth configuration examples in the repository
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "betterAuth" {} \; | head -10Repository: TanStack/router
Length of output: 309
🏁 Script executed:
# Read the full auth.ts file to see the complete configuration
cat -n ./examples/react/start-basic-better-auth/src/utils/auth.tsRepository: TanStack/router
Length of output: 952
🏁 Script executed:
# Check the auth-client.ts file for context
cat -n ./examples/react/start-basic-better-auth/src/utils/auth-client.tsRepository: TanStack/router
Length of output: 180
🏁 Script executed:
# Check if there's any .env or documentation about setup
find ./examples/react/start-basic-better-auth -name ".env*" -o -name "README*" -o -name "*.md" | head -10Repository: TanStack/router
Length of output: 163
🏁 Script executed:
# Look at the solid example's auth configuration for comparison
cat -n ./examples/solid/start-convex-better-auth/convex/auth.ts | head -50Repository: TanStack/router
Length of output: 1897
🏁 Script executed:
# Check the .env.example to see what environment variables are documented
cat -n ./examples/react/start-basic-better-auth/.env.exampleRepository: TanStack/router
Length of output: 454
🏁 Script executed:
# Check the README for setup instructions
cat -n ./examples/react/start-basic-better-auth/README.mdRepository: TanStack/router
Length of output: 2270
Add missing baseURL and trustedOrigins configuration.
Better Auth requires baseURL (or BETTER_AUTH_URL environment variable) and trustedOrigins for OAuth callbacks and CSRF protection to work correctly. The .env.example includes BETTER_AUTH_URL, but trustedOrigins is completely missing from both the configuration and environment variables. Without trustedOrigins, the OAuth flow will fail with CORS/CSRF errors in production. Add both explicitly:
export const auth = betterAuth({
baseURL: process.env.BETTER_AUTH_URL,
trustedOrigins: [process.env.BETTER_AUTH_URL],
socialProviders: {
// ... rest of config
},
// ...
});
Then ensure .env.example and .env include all required origins (local dev, production URLs, and any other allowed origins).
🤖 Prompt for AI Agents
In @examples/react/start-basic-better-auth/src/utils/auth.ts around lines 7 -
25, The Better Auth config in the exported auth (created via betterAuth) is
missing baseURL and trustedOrigins which breaks OAuth/CSRF in production; update
the betterAuth call for auth to include baseURL: process.env.BETTER_AUTH_URL and
trustedOrigins: [process.env.BETTER_AUTH_URL] (or an array of allowed origins
including localhost/dev and production URLs) alongside the existing
socialProviders and plugins, and then add the corresponding BETTER_AUTH_URL (and
any additional allowed origins) to your environment files/.env.example.
| clientId: process.env.GITHUB_CLIENT_ID!, | ||
| clientSecret: process.env.GITHUB_CLIENT_SECRET!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add runtime validation for required environment variables.
The non-null assertions (!) on process.env.GITHUB_CLIENT_ID and process.env.GITHUB_CLIENT_SECRET will cause the application to crash with a cryptic error if these variables are missing. For better developer experience, validate these at startup with clear error messages.
♻️ Proposed fix with validation
+if (!process.env.GITHUB_CLIENT_ID || !process.env.GITHUB_CLIENT_SECRET) {
+ throw new Error(
+ 'Missing required environment variables: GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET. ' +
+ 'Please copy .env.example to .env and fill in your GitHub OAuth credentials.'
+ );
+}
+
export const auth = betterAuth({
socialProviders: {
github: {
- clientId: process.env.GITHUB_CLIENT_ID!,
- clientSecret: process.env.GITHUB_CLIENT_SECRET!,
+ clientId: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
},🤖 Prompt for AI Agents
In @examples/react/start-basic-better-auth/src/utils/auth.ts around lines 10 -
11, Replace the non-null assertions for process.env.GITHUB_CLIENT_ID and
process.env.GITHUB_CLIENT_SECRET with a runtime check that validates these env
vars at startup: read process.env.GITHUB_CLIENT_ID and
process.env.GITHUB_CLIENT_SECRET into variables, if either is missing throw or
log a clear error (e.g., "GITHUB_CLIENT_ID is required") and exit, then use
those validated variables for clientId and clientSecret; reference the
clientId/clientSecret fields in the exported auth config (or the function that
builds it) so the values are guaranteed present.
|
I started from the Authjs example and adapted it for Better Auth. |
This pull request introduces a new example project,
start-basic-better-auth, demonstrating authentication integration between TanStack Start and Better Auth using GitHub OAuth. The project includes all necessary configuration, routing, authentication logic, and supporting documentation. It provides a ready-to-use template for developers to implement secure authentication and protected routes in a React application.Authentication and Session Management:
src/utils/auth.ts, enabling GitHub OAuth and stateless session management via cookies.src/utils/auth-client.tsfor use in React components./api/auth/*to process authentication requests through Better Auth.Routing and Protected Pages:
src/routes/__root.tsx, fetching the session and providing navigation with sign-in/sign-out logic.src/routes/protected.tsxandsrc/routes/login.tsx) that redirect users based on authentication status. [1] [2]Project Structure and Tooling:
package.jsonwith dependencies for TanStack Router/Start, Better Auth, React 19, Tailwind CSS, and Vite tooling.tsconfig.json) and environment variable example (.env.example) for easy setup and development. [1] [2].gitignoreto exclude build artifacts and sensitive files.Documentation and UI Components:
README.mdwith setup instructions, OAuth configuration steps, and key file references for authentication integration.DefaultCatchBoundary.tsx), not found pages (NotFound.tsx), and applies Tailwind CSS styling. [1] [2] [3]This example provides a robust starting point for secure authentication workflows in TanStack Start applications.
Summary by CodeRabbit
Documentation
New Features
✏️ Tip: You can customize this high-level summary in your review settings.