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
10 changes: 10 additions & 0 deletions examples/react/start-basic-better-auth/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Better Auth Configuration
# Generate a secure secret with: openssl rand -base64 32
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
30 changes: 30 additions & 0 deletions examples/react/start-basic-better-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"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.170.17",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-start": "^1.168.27",
"better-auth": "^1.6.23",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.2",
"@types/node": "^22.5.4",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^6.0.1",
"tailwindcss": "^4.2.2",
"typescript": "^6.0.2",
"vite": "^8.0.14"
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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>
)
}
26 changes: 26 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,26 @@
import type { ReactNode } from 'react'
import { Link } from '@tanstack/react-router'

export function NotFound({ children }: { children?: ReactNode }) {
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