Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/react/start-basic-better-auth/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Better Auth Configuration
BETTER_AUTH_SECRET=your-secret-key-here-min-32-chars-long

# Better Auth URL (base URL, no /api/auth suffix)
BETTER_AUTH_URL=http://localhost:10000

# GitHub OAuth Configuration (https://github.com/settings/developers)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
6 changes: 6 additions & 0 deletions examples/react/start-basic-better-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
.env
*.local
.DS_Store
.tanstack
66 changes: 66 additions & 0 deletions examples/react/start-basic-better-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# TanStack Start - Better Auth Example

A TanStack Start example demonstrating authentication with Better Auth.

- [TanStack Router Docs](https://tanstack.com/router)
- [Better Auth Documentation](https://www.better-auth.com/)

## Start a new project based on this example

To start a new project based on this example, run:

```sh
npx gitpick TanStack/router/tree/main/examples/react/start-basic-better-auth start-basic-better-auth
```

## Setup

This example requires environment variables for Better Auth configuration. Copy the `.env.example` file to `.env` and fill in your GitHub OAuth credentials:

```sh
cp .env.example .env
```

### GitHub OAuth Setup

1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Click "New OAuth App"
3. Fill in:
- Application name: Your app name
- Homepage URL: `http://localhost:10000`
- Authorization callback URL: `http://localhost:10000/api/auth/callback/github`
4. Copy the Client ID and Client Secret to your `.env` file

## Getting Started

From your terminal:

```sh
pnpm install
pnpm dev
```

This starts your app in development mode, rebuilding assets on file changes.

## Build

To build the app for production:

```sh
pnpm build
```

## Authentication Configuration

This example demonstrates how to integrate Better Auth with TanStack Start. Check the source code for examples of:

- Configuring social authentication providers
- Protecting routes with authentication
- Accessing session data in server functions

### Key Files

- `src/utils/auth.ts` - Better Auth server configuration
- `src/utils/auth-client.ts` - Better Auth client setup
- `src/routes/__root.tsx` - Session fetching and navigation
- `src/routes/protected.tsx` - Example of a protected route
32 changes: 32 additions & 0 deletions examples/react/start-basic-better-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,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"
}
}
Comment on lines +1 to +32
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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
ErrorComponent,
Link,
rootRouteId,
useMatch,
useRouter,
} from "@tanstack/react-router";
import type { ErrorComponentProps } from "@tanstack/react-router";

export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
const router = useRouter();
const isRoot = useMatch({
strict: false,
select: (state) => state.id === rootRouteId,
});

console.error("DefaultCatchBoundary Error:", error);

return (
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
<ErrorComponent error={error} />
<div className="flex gap-2 items-center flex-wrap">
<button
onClick={() => {
router.invalidate();
}}
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
>
Try Again
</button>
{isRoot ? (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
>
Home
</Link>
) : (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
onClick={(e) => {
e.preventDefault();
window.history.back();
}}
>
Go Back
</Link>
)}
</div>
</div>
);
}
25 changes: 25 additions & 0 deletions examples/react/start-basic-better-auth/src/components/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -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.

return (
<div className="space-y-2 p-2">
<div className="text-gray-600 dark:text-gray-400">
{children || <p>The page you are looking for does not exist.</p>}
</div>
<p className="flex items-center gap-2 flex-wrap">
<button
onClick={() => window.history.back()}
className="bg-emerald-500 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
>
Go back
</button>
<Link
to="/"
className="bg-cyan-600 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
>
Start Over
</Link>
</p>
</div>
);
}
122 changes: 122 additions & 0 deletions examples/react/start-basic-better-auth/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as ProtectedRouteImport } from './routes/protected'
import { Route as LoginRouteImport } from './routes/login'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'

const ProtectedRoute = ProtectedRouteImport.update({
id: '/protected',
path: '/protected',
getParentRoute: () => rootRouteImport,
} as any)
const LoginRoute = LoginRouteImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
id: '/api/auth/$',
path: '/api/auth/$',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/protected': typeof ProtectedRoute
'/api/auth/$': typeof ApiAuthSplatRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/protected': typeof ProtectedRoute
'/api/auth/$': typeof ApiAuthSplatRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/protected': typeof ProtectedRoute
'/api/auth/$': typeof ApiAuthSplatRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/login' | '/protected' | '/api/auth/$'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/login' | '/protected' | '/api/auth/$'
id: '__root__' | '/' | '/login' | '/protected' | '/api/auth/$'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
LoginRoute: typeof LoginRoute
ProtectedRoute: typeof ProtectedRoute
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/protected': {
id: '/protected'
path: '/protected'
fullPath: '/protected'
preLoaderRoute: typeof ProtectedRouteImport
parentRoute: typeof rootRouteImport
}
'/login': {
id: '/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/api/auth/$': {
id: '/api/auth/$'
path: '/api/auth/$'
fullPath: '/api/auth/$'
preLoaderRoute: typeof ApiAuthSplatRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
LoginRoute: LoginRoute,
ProtectedRoute: ProtectedRoute,
ApiAuthSplatRoute: ApiAuthSplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
19 changes: 19 additions & 0 deletions examples/react/start-basic-better-auth/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "./components/DefaultCatchBoundary";
import { NotFound } from "./components/NotFound";
import type { RouterContext } from "./routes/__root";

export function getRouter() {
const router = createRouter({
routeTree,
defaultPreload: "intent",
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => <NotFound />,
scrollRestoration: true,
context: {
session: null,
} satisfies RouterContext,
});
return router;
}
Loading