Skip to content

Conversation

@MrHertal
Copy link

@MrHertal MrHertal commented Jan 10, 2026

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:

  • Implements Better Auth server configuration in src/utils/auth.ts, enabling GitHub OAuth and stateless session management via cookies.
  • Sets up the Better Auth client in src/utils/auth-client.ts for use in React components.
  • Adds an API handler route for /api/auth/* to process authentication requests through Better Auth.

Routing and Protected Pages:

  • Defines the root route and context in src/routes/__root.tsx, fetching the session and providing navigation with sign-in/sign-out logic.
  • Implements protected and login routes (src/routes/protected.tsx and src/routes/login.tsx) that redirect users based on authentication status. [1] [2]
  • Generates the route tree and router setup, including custom error and not-found boundaries. [1] [2]

Project Structure and Tooling:

  • Provides a complete package.json with dependencies for TanStack Router/Start, Better Auth, React 19, Tailwind CSS, and Vite tooling.
  • Adds TypeScript configuration (tsconfig.json) and environment variable example (.env.example) for easy setup and development. [1] [2]
  • Includes .gitignore to exclude build artifacts and sensitive files.

Documentation and UI Components:

  • Adds a comprehensive README.md with setup instructions, OAuth configuration steps, and key file references for authentication integration.
  • Implements reusable UI components for error handling (DefaultCatchBoundary.tsx), not found pages (NotFound.tsx), and applies Tailwind CSS styling. [1] [2] [3]
  • Sample pages display authentication status and session data for debugging and demonstration. [1] [2]

This example provides a robust starting point for secure authentication workflows in TanStack Start applications.

Summary by CodeRabbit

  • Documentation

    • Added comprehensive example project documentation with setup and authentication integration instructions.
  • New Features

    • New example project demonstrating React authentication with GitHub OAuth login, route protection, session management, and sign-in/sign-out functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Configuration & Tooling
.env.example, .gitignore, package.json, tsconfig.json, vite.config.ts
Project setup including environment variables (BETTER_AUTH_SECRET, GITHUB credentials), build dependencies, TypeScript strict mode, and Vite dev server on port 10000 with Tailwind/TanStack plugins.
Documentation
README.md
Project guide covering environment setup, GitHub OAuth App configuration (callback URLs: http://localhost:10000/api/auth/callback/github), installation, development/build commands, and key authentication files.
Router Configuration
src/router.tsx, src/routeTree.gen.ts
Router factory function and auto-generated route tree wiring together all routes with default error/not-found handlers and scroll restoration; includes typed route metadata and FileRoutesByPath module augmentation.
Root & API Routes
src/routes/__root.tsx, src/routes/api/auth/$.ts
Root layout with server-side session fetching, NavBar with sign-in/out flows, and catch-all API route handler delegating auth requests to better-auth middleware.
Page Routes
src/routes/index.tsx, src/routes/login.tsx, src/routes/protected.tsx
Home page displaying session/user info, GitHub OAuth sign-in page, and protected route with beforeLoad guard redirecting unauthenticated users to /login.
Components
src/components/DefaultCatchBoundary.tsx, src/components/NotFound.tsx
Error boundary with retry/navigation actions and 404 fallback component.
Utilities & Styling
src/utils/auth-client.ts, src/utils/auth.ts, src/styles/app.css
Authentication client instantiation, better-auth configuration with GitHub social provider and stateless cookie-based sessions, and Tailwind CSS import.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • TanStack/router#5847 — Adds similar TanStack Start example app with generated routeTree.gen.ts and getRouter implementation
  • TanStack/router#5849 — Adds another TanStack Start example with routeTree.gen.ts and router.tsx pattern

Suggested labels

documentation

Suggested reviewers

  • birkskyum
  • schiller-manuel

Poem

🐰 A rabbit hops through auth's green door,
With TanStack routes and sessions galore,
GitHub OAuth dancing with Tailwind's grace,
Protected pages in their rightful place—
Better Auth blooms in this example so fine! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'examples: add tanstack start with better auth example' directly and accurately summarizes the main change—adding a new example project demonstrating TanStack Start integration with Better Auth.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 adding forceConsistentCasingInFileNames.
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 removing strictPort for better developer experience.

Setting strictPort: true will 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 to false to 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.session is already verified as truthy on line 100, the optional chaining on routeContext.session?.user?.name and routeContext.session?.user?.email is redundant. While this doesn't cause issues, simplifying to routeContext.session.user?.name improves 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-long
examples/react/start-basic-better-auth/src/router.tsx (1)

11-12: Use consistent component reference pattern.

The defaultErrorComponent uses a direct component reference while defaultNotFoundComponent wraps 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.image without 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 /login with no way to return to /protected after signing in; consider passing a search param (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 adding loading="lazy", referrerPolicy="no-referrer" (common for 3rd-party avatars), and explicit width/height to 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 to router.navigate({ to: '/' }) (or just let the Link navigate) when history.length <= 1.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fad5fdb and 99bd881.

📒 Files selected for processing (18)
  • examples/react/start-basic-better-auth/.env.example
  • examples/react/start-basic-better-auth/.gitignore
  • examples/react/start-basic-better-auth/README.md
  • examples/react/start-basic-better-auth/package.json
  • examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx
  • examples/react/start-basic-better-auth/src/components/NotFound.tsx
  • examples/react/start-basic-better-auth/src/routeTree.gen.ts
  • examples/react/start-basic-better-auth/src/router.tsx
  • examples/react/start-basic-better-auth/src/routes/__root.tsx
  • examples/react/start-basic-better-auth/src/routes/api/auth/$.ts
  • examples/react/start-basic-better-auth/src/routes/index.tsx
  • examples/react/start-basic-better-auth/src/routes/login.tsx
  • examples/react/start-basic-better-auth/src/routes/protected.tsx
  • examples/react/start-basic-better-auth/src/styles/app.css
  • examples/react/start-basic-better-auth/src/utils/auth-client.ts
  • examples/react/start-basic-better-auth/src/utils/auth.ts
  • examples/react/start-basic-better-auth/tsconfig.json
  • examples/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.tsx
  • examples/react/start-basic-better-auth/src/routes/api/auth/$.ts
  • examples/react/start-basic-better-auth/src/routes/index.tsx
  • examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx
  • examples/react/start-basic-better-auth/src/utils/auth.ts
  • examples/react/start-basic-better-auth/src/routes/__root.tsx
  • examples/react/start-basic-better-auth/src/components/NotFound.tsx
  • examples/react/start-basic-better-auth/src/utils/auth-client.ts
  • examples/react/start-basic-better-auth/vite.config.ts
  • examples/react/start-basic-better-auth/src/routes/protected.tsx
  • examples/react/start-basic-better-auth/src/routeTree.gen.ts
  • examples/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.tsx
  • examples/react/start-basic-better-auth/src/routes/api/auth/$.ts
  • examples/react/start-basic-better-auth/src/routes/index.tsx
  • examples/react/start-basic-better-auth/src/components/DefaultCatchBoundary.tsx
  • examples/react/start-basic-better-auth/src/utils/auth.ts
  • examples/react/start-basic-better-auth/src/routes/__root.tsx
  • examples/react/start-basic-better-auth/src/components/NotFound.tsx
  • examples/react/start-basic-better-auth/src/utils/auth-client.ts
  • examples/react/start-basic-better-auth/vite.config.ts
  • examples/react/start-basic-better-auth/src/routes/protected.tsx
  • examples/react/start-basic-better-auth/src/routeTree.gen.ts
  • examples/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.tsx
  • examples/react/start-basic-better-auth/src/routes/api/auth/$.ts
  • examples/react/start-basic-better-auth/src/routes/index.tsx
  • examples/react/start-basic-better-auth/src/routes/__root.tsx
  • examples/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.tsx
  • examples/react/start-basic-better-auth/src/routes/protected.tsx
  • 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: 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.json
  • examples/react/start-basic-better-auth/src/routeTree.gen.ts
  • examples/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/.gitignore
  • examples/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/.gitignore
  • examples/react/start-basic-better-auth/src/routes/protected.tsx
  • examples/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 separate auth instance. The default basePath of /api/auth matches your route configuration at routes/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 RouterContext ensures type safety while allowing TypeScript to infer the exact type. The initial session: null value will be properly populated by the root route's beforeLoad hook.

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 other DefaultCatchBoundary patterns (router invalidate + root-aware navigation).

Comment on lines +1 to +32
{
"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"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 }) {
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +81 to +85
const handleSignOut = async () => {
await authClient.signOut();
await router.invalidate();
await router.navigate({ to: "/" });
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +47 to +52
<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +7 to +25
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,
},
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Better Auth baseURL trustedOrigins configuration OAuth GitHub

💡 Result:

Key points and recommended config for Better Auth + GitHub OAuth

  1. 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]

  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:

  1. 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]

  2. 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]
  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]
  1. 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 -20

Repository: 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 -10

Repository: 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.ts

Repository: 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.ts

Repository: 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 -10

Repository: 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 -50

Repository: 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.example

Repository: TanStack/router

Length of output: 454


🏁 Script executed:

# Check the README for setup instructions
cat -n ./examples/react/start-basic-better-auth/README.md

Repository: 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.

Comment on lines +10 to +11
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

@MrHertal
Copy link
Author

I started from the Authjs example and adapted it for Better Auth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant