Skip to content

Tanstack Start + Ory Kratos#6592

Open
bennettscoresby wants to merge 3 commits intoTanStack:mainfrom
bennettscoresby:main
Open

Tanstack Start + Ory Kratos#6592
bennettscoresby wants to merge 3 commits intoTanStack:mainfrom
bennettscoresby:main

Conversation

@bennettscoresby
Copy link

@bennettscoresby bennettscoresby commented Feb 3, 2026

Summary

Adds a new example (examples/react/start-basic-ory) demonstrating how to wire
Ory Kratos identity management into a TanStack Start app using file-based routing.

What's in the example

  • Session via route context — the root route's beforeLoad calls a server
    function to fetch the active Ory session and publishes it as AuthContext.
    Child routes read it with Route.useRouteContext() — no global state store
    needed.
  • Protected routes via a pathless layout route — _authenticated.tsx guards
    its children (currently /profile) by checking context.auth.isAuthenticated
    in beforeLoad and redirecting to the Ory login flow if the session is missing.
  • Server functions only for Ory calls — src/utils/orySession.ts uses
    createServerFn to proxy session and logout requests to Kratos. Ory SDK
    credentials and cookies never touch the client bundle.
  • In-memory session cache — avoids a round-trip to Kratos on every
    navigation within the TTL window.
  • Two local-dev paths documented in the README:
    • Ory Network + ory tunnel (no Docker, cloud-backed)
    • Self-hosted Kratos via the upstream docker-compose quickstart

Dependencies added
Only @ory/client-fetch (the official Ory SDK) is added. Everything else is already in the TanStack Start baseline (react, vite, @tanstack/*).

Summary by CodeRabbit

  • New Features

    • Added a new TanStack Start example project demonstrating authentication integration with Ory Kratos, including login/logout flows and protected routes.
  • Documentation

    • Included setup guide covering deployment options and development instructions for the new example project.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new TanStack Start example project demonstrating Ory authentication integration, featuring routing infrastructure, protected routes with authentication guards, server-side session management, and a complete React UI with login/logout functionality.

Changes

Cohort / File(s) Summary
Configuration Files
examples/react/start-basic-ory/.gitignore, examples/react/start-basic-ory/package.json, examples/react/start-basic-ory/tsconfig.json, examples/react/start-basic-ory/vite.config.ts
Project setup including dependency declarations (React, TanStack libraries, Ory client), build tooling configuration (Vite with React plugin and tsconfig paths), and standard development scripts.
Routing Infrastructure
examples/react/start-basic-ory/src/router.tsx, examples/react/start-basic-ory/src/routeTree.gen.ts
Generated route tree with type definitions and router initialization function exposing router instance through TanStack Router registration mechanism.
Route Components
examples/react/start-basic-ory/src/routes/__root.tsx, examples/react/start-basic-ory/src/routes/_authenticated.tsx, examples/react/start-basic-ory/src/routes/_authenticated/profile.tsx, examples/react/start-basic-ory/src/routes/index.tsx
Root route with authentication context and navigation header, protected route with auth guard redirecting unauthenticated users, profile page displaying user email, and landing page with feature showcase.
Utilities & Styling
examples/react/start-basic-ory/src/utils/orySession.ts, examples/react/start-basic-ory/src/styles/app.css
Server-side session management utilities providing cached Ory session retrieval and logout flow creation; global stylesheet with reset and baseline dark-mode styling.
Documentation
examples/react/start-basic-ory/README.md
Setup instructions covering Ory Network and self-hosted Docker options, environment configuration, development workflow, and architectural overview of route guards and server functions.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser
    participant App as TanStack Start App
    participant OryServer as Ory Server
    participant SessionCache as Session Cache
    
    Browser->>App: Navigate to protected route
    App->>App: Route beforeLoad hook executes
    App->>App: Call getOrySession()
    App->>SessionCache: Check cache for session
    
    alt Cache hit
        SessionCache-->>App: Return cached session
    else Cache miss or expired
        App->>OryServer: GET /sessions/whoami (with cookie)
        OryServer-->>App: Return session data
        App->>SessionCache: Store session (TTL: 10 min)
    end
    
    App->>App: Evaluate isAuthenticated
    
    alt Authenticated
        App->>Browser: Render protected route
        Browser->>App: User clicks logout
        App->>App: Call getLogoutUrl()
        App->>OryServer: POST /self-service/logout/flows (with cookie)
        OryServer-->>App: Return logout_url
        App->>Browser: Redirect to logout_url
    else Not authenticated
        App->>Browser: Redirect to login URL
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

documentation

Suggested reviewers

  • brenelz
  • chorobin

Poem

🐰 Hopping through auth flows with glee,
Ory meets TanStack, protected and free,
Sessions cached swift, routes guard the way,
A new example blooms—hip hip hooray!
🔐✨

🚥 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 'Tanstack Start + Ory Kratos' directly and clearly describes the main addition: a new example integrating TanStack Start with Ory Kratos identity management.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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: 5

🤖 Fix all issues with AI agents
In `@examples/react/start-basic-ory/package.json`:
- Around line 12-19: The TanStack packages in package.json are pinned to
published versions causing potential desync; update the internal dependencies
"@tanstack/react-router", "@tanstack/react-router-devtools", and
"@tanstack/react-start" to use the workspace protocol (workspace:*) instead of
the fixed versions so local workspace packages are used during development and
CI.

In `@examples/react/start-basic-ory/src/router.tsx`:
- Around line 5-10: The defaultErrorComponent passed into createRouter currently
renders err.error.stack which can leak internals; update the
defaultErrorComponent (used in createRouter) to check the environment (e.g.,
process.env.NODE_ENV === 'development') and only render the full stack in
development, otherwise render a safe generic message (or err.error.message
minimally) and handle missing error gracefully; change the function referenced
as defaultErrorComponent to perform this conditional rendering using
ErrorComponentProps.error.

In `@examples/react/start-basic-ory/src/routes/__root.tsx`:
- Around line 11-13: Alphabetize the named imports and separate the type-only
import onto its own line: change the named import from
getOrySession,getLogoutUrl to getLogoutUrl,getOrySession (from
'~/utils/orySession') and move type { OrySession } into a dedicated type-only
import line (import type { OrySession } from '~/utils/orySession'); keep appCss
import first. This fixes the sort-imports/import/order warnings referencing
getOrySession, getLogoutUrl, and OrySession.

In `@examples/react/start-basic-ory/src/routes/_authenticated.tsx`:
- Line 1: The named imports from '@tanstack/react-router' are not in the order
expected by ESLint's sort-imports rule; reorder the import specifiers
(createFileRoute, Outlet, redirect) so they are sorted alphabetically
(case-insensitive) per the rule, update the import statement using the sorted
order, and re-run lint to confirm the warning is resolved; look for and modify
the import line that currently imports createFileRoute, Outlet, redirect.

In `@examples/react/start-basic-ory/src/routes/index.tsx`:
- Line 1: The file references React.CSSProperties (at the use of inlineStyles or
similar around the symbol React.CSSProperties) without importing React, causing
strict TypeScript to fail; add a type-only import "import type { CSSProperties }
from 'react'" at the top and replace uses of React.CSSProperties with the
imported CSSProperties type (or use CSSProperties directly where the type is
declared) so the code compiles under strict mode.
🧹 Nitpick comments (1)
examples/react/start-basic-ory/src/utils/orySession.ts (1)

12-35: Prevent unbounded cache growth by pruning expired entries.
sessionCache only evicts the current key, so unique cookies can accumulate over time. A small prune step keeps memory bounded.

♻️ Suggested change
 const SESSION_CACHE_TTL = 600_000 // 10 minutes
 const sessionCache = new Map<string, { data: Session | null; expiresAt: number }>()
+
+const pruneSessionCache = () => {
+  const now = Date.now()
+  for (const [key, entry] of sessionCache) {
+    if (entry.expiresAt <= now) {
+      sessionCache.delete(key)
+    }
+  }
+}
 
 export const getOrySession = createServerFn({ method: 'GET' }).handler(async () => {
     const req = getRequest()
     const cookie = req.headers.get('cookie') ?? ''
+
+    pruneSessionCache()
 
     const cached = sessionCache.get(cookie)
     if (cached && Date.now() < cached.expiresAt) {
         return cached.data
     }

Comment on lines +12 to +19
"dependencies": {
"@ory/client-fetch": "^1.22.22",
"@tanstack/react-router": "^1.158.0",
"@tanstack/react-router-devtools": "^1.158.0",
"@tanstack/react-start": "^1.158.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^3.24.2"
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 internal TanStack dependencies.
Pinning internal packages to published versions can desync the example from the workspace during local dev and CI. Please switch the TanStack packages to the workspace protocol.

🔧 Suggested change
-    "@tanstack/react-router": "^1.158.0",
-    "@tanstack/react-router-devtools": "^1.158.0",
-    "@tanstack/react-start": "^1.158.0",
+    "@tanstack/react-router": "workspace:*",
+    "@tanstack/react-router-devtools": "workspace:*",
+    "@tanstack/react-start": "workspace:*",

As per coding guidelines: **/package.json: Use workspace protocol workspace:* for internal dependencies in package.json files.

📝 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
"dependencies": {
"@ory/client-fetch": "^1.22.22",
"@tanstack/react-router": "^1.158.0",
"@tanstack/react-router-devtools": "^1.158.0",
"@tanstack/react-start": "^1.158.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^3.24.2"
"dependencies": {
"@ory/client-fetch": "^1.22.22",
"@tanstack/react-router": "workspace:*",
"@tanstack/react-router-devtools": "workspace:*",
"@tanstack/react-start": "workspace:*",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^3.24.2"
🤖 Prompt for AI Agents
In `@examples/react/start-basic-ory/package.json` around lines 12 - 19, The
TanStack packages in package.json are pinned to published versions causing
potential desync; update the internal dependencies "@tanstack/react-router",
"@tanstack/react-router-devtools", and "@tanstack/react-start" to use the
workspace protocol (workspace:*) instead of the fixed versions so local
workspace packages are used during development and CI.

Comment on lines +5 to +10
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: (err) => <p>{err.error.stack}</p>,
defaultNotFoundComponent: () => <p>not found</p>,
scrollRestoration: true,
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

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the router file
find . -type f -path "*examples/react/start-basic-ory/src/router.tsx"

Repository: TanStack/router

Length of output: 106


🏁 Script executed:

# Get the actual file content around lines 5-10
head -20 examples/react/start-basic-ory/src/router.tsx

Repository: TanStack/router

Length of output: 546


🏁 Script executed:

# Check if this is a Vite project (to verify import.meta.env availability)
cat examples/react/start-basic-ory/package.json | head -30

Repository: TanStack/router

Length of output: 866


🏁 Script executed:

# Search for other error handling patterns in the codebase
rg "defaultErrorComponent|error\.stack" --type tsx --type ts | head -20

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

# Check the error object structure in TanStack Router documentation or type definitions
rg "defaultErrorComponent" --type ts -A 3 | head -40

Repository: TanStack/router

Length of output: 2323


🏁 Script executed:

# Check if ESLint router plugin is installed
cat examples/react/start-basic-ory/package.json | rg -i "eslint|router"

Repository: TanStack/router

Length of output: 151


🏁 Script executed:

# Search for error handling in other examples
find examples -name "*.tsx" -o -name "*.ts" | xargs grep -l "defaultErrorComponent" 2>/dev/null | head -5

Repository: TanStack/router

Length of output: 309


🏁 Script executed:

# Check the start-bare example router configuration
cat examples/react/start-bare/src/router.tsx

Repository: TanStack/router

Length of output: 546


🏁 Script executed:

# Search for type definitions or interface for defaultErrorComponent
rg "interface.*ErrorComponent|type.*ErrorComponent" --type ts | head -20

Repository: TanStack/router

Length of output: 2557


🏁 Script executed:

# Check the exact error parameter signature in React Router package
rg "defaultErrorComponent.*\(" packages/react-router/src -A 2 | head -30

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Find React Router's ErrorComponentProps definition
rg "ErrorComponentProps" packages/react-router/src -A 5 | head -40

Repository: TanStack/router

Length of output: 1135


🏁 Script executed:

# Check how error is structured in React Router
rg "error.*stack|Error.*stack" packages/react-router/src -B 2 -A 2 | head -40

Repository: TanStack/router

Length of output: 1209


🏁 Script executed:

# Look for type definitions in react-router
find packages/react-router -name "*.ts" -o -name "*.tsx" | xargs grep -l "interface.*ErrorComponent\|type.*ErrorComponent" 2>/dev/null

Repository: TanStack/router

Length of output: 146


🏁 Script executed:

# Find ErrorComponentProps definition in router-core
rg "interface ErrorComponentProps|type ErrorComponentProps" packages/router-core/src -A 3

Repository: TanStack/router

Length of output: 318


🏁 Script executed:

# Check the error structure in router-core
rg "error.*Error\|Error.*{" packages/router-core/src -B 2 -A 2 | grep -A 5 "error:"

Repository: TanStack/router

Length of output: 166


Avoid exposing stack traces in production.

Rendering error.stack directly can leak internal details. The defaultErrorComponent receives ErrorComponentProps with an error property; stack traces should be shown only in development and a generic message in production.

🔒 Proposed fix
-    defaultErrorComponent: (err) => <p>{err.error.stack}</p>,
+    defaultErrorComponent: ({ error }) =>
+      import.meta.env.DEV ? (
+        <pre>{error.stack}</pre>
+      ) : (
+        <p>Something went wrong.</p>
+      ),
📝 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
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: (err) => <p>{err.error.stack}</p>,
defaultNotFoundComponent: () => <p>not found</p>,
scrollRestoration: true,
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: ({ error }) =>
import.meta.env.DEV ? (
<pre>{error.stack}</pre>
) : (
<p>Something went wrong.</p>
),
defaultNotFoundComponent: () => <p>not found</p>,
scrollRestoration: true,
🤖 Prompt for AI Agents
In `@examples/react/start-basic-ory/src/router.tsx` around lines 5 - 10, The
defaultErrorComponent passed into createRouter currently renders err.error.stack
which can leak internals; update the defaultErrorComponent (used in
createRouter) to check the environment (e.g., process.env.NODE_ENV ===
'development') and only render the full stack in development, otherwise render a
safe generic message (or err.error.message minimally) and handle missing error
gracefully; change the function referenced as defaultErrorComponent to perform
this conditional rendering using ErrorComponentProps.error.

Comment on lines +11 to +13
import appCss from '~/styles/app.css?url'
import { getOrySession, getLogoutUrl } from '~/utils/orySession'
import type { OrySession } from '~/utils/orySession'
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 | 🟡 Minor

Resolve ESLint import ordering warnings.

sort-imports and import/order are flagging the orySession import member order and the type-only import placement.

🧹 Proposed fix
-import appCss from '~/styles/app.css?url'
-import { getOrySession, getLogoutUrl } from '~/utils/orySession'
-import type { OrySession } from '~/utils/orySession'
+import { getLogoutUrl, getOrySession, type OrySession } from '~/utils/orySession'
+import appCss from '~/styles/app.css?url'
📝 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
import appCss from '~/styles/app.css?url'
import { getOrySession, getLogoutUrl } from '~/utils/orySession'
import type { OrySession } from '~/utils/orySession'
import { getLogoutUrl, getOrySession, type OrySession } from '~/utils/orySession'
import appCss from '~/styles/app.css?url'
🧰 Tools
🪛 ESLint

[error] 12-12: Member 'getLogoutUrl' of the import declaration should be sorted alphabetically.

(sort-imports)


[error] 13-13: ~/utils/orySession type import should occur before import of ~/styles/app.css?url

(import/order)

🤖 Prompt for AI Agents
In `@examples/react/start-basic-ory/src/routes/__root.tsx` around lines 11 - 13,
Alphabetize the named imports and separate the type-only import onto its own
line: change the named import from getOrySession,getLogoutUrl to
getLogoutUrl,getOrySession (from '~/utils/orySession') and move type {
OrySession } into a dedicated type-only import line (import type { OrySession }
from '~/utils/orySession'); keep appCss import first. This fixes the
sort-imports/import/order warnings referencing getOrySession, getLogoutUrl, and
OrySession.

@@ -0,0 +1,12 @@
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
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 | 🟡 Minor

Fix ESLint import member ordering.

sort-imports is flagging member order in the router import. Reorder to satisfy lint.

🧹 Proposed fix
-import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
+import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'
📝 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
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'
🧰 Tools
🪛 ESLint

[error] 1-1: Member 'Outlet' of the import declaration should be sorted alphabetically.

(sort-imports)

🤖 Prompt for AI Agents
In `@examples/react/start-basic-ory/src/routes/_authenticated.tsx` at line 1, The
named imports from '@tanstack/react-router' are not in the order expected by
ESLint's sort-imports rule; reorder the import specifiers (createFileRoute,
Outlet, redirect) so they are sorted alphabetically (case-insensitive) per the
rule, update the import statement using the sorted order, and re-run lint to
confirm the warning is resolved; look for and modify the import line that
currently imports createFileRoute, Outlet, redirect.

@@ -0,0 +1,194 @@
import { createFileRoute } from '@tanstack/react-router'
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

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file in question
fd -type f -name "index.tsx" | grep "start-basic-ory" | head -5

Repository: TanStack/router

Length of output: 229


🏁 Script executed:

# Also check the overall repository structure to understand context
git ls-files "examples/react/start-basic-ory/" | head -20

Repository: TanStack/router

Length of output: 861


🏁 Script executed:

# Look for tsconfig files
fd -type f -name "tsconfig*.json" | head -10

Repository: TanStack/router

Length of output: 229


🏁 Script executed:

# Read the index.tsx file
cat -n examples/react/start-basic-ory/src/routes/index.tsx

Repository: TanStack/router

Length of output: 6239


🏁 Script executed:

# Read the tsconfig.json
cat -n examples/react/start-basic-ory/tsconfig.json

Repository: TanStack/router

Length of output: 743


🏁 Script executed:

# Check root tsconfig if it exists
cat -n tsconfig.json 2>/dev/null | head -50

Repository: TanStack/router

Length of output: 1042


Import React types for CSSProperties.

React.CSSProperties is referenced at line 67 without importing React. With strict mode enabled and no allowUmdGlobalAccess configured, this will fail TypeScript compilation. Add a type-only import of CSSProperties from react and use it directly.

Proposed fix
import { createFileRoute } from '@tanstack/react-router'
+import type { CSSProperties } from 'react'

export const Route = createFileRoute('/')({
  component: RouteComponent,
})

const badges = ['File-based Routing', 'Server Functions', 'Session Auth', 'Protected Routes']

function RouteComponent() {
  return (
    <main style={styles.page}>
      ...
    </main>
  )
}

-const styles: Record<string, React.CSSProperties> = {
+const styles: Record<string, CSSProperties> = {
🤖 Prompt for AI Agents
In `@examples/react/start-basic-ory/src/routes/index.tsx` at line 1, The file
references React.CSSProperties (at the use of inlineStyles or similar around the
symbol React.CSSProperties) without importing React, causing strict TypeScript
to fail; add a type-only import "import type { CSSProperties } from 'react'" at
the top and replace uses of React.CSSProperties with the imported CSSProperties
type (or use CSSProperties directly where the type is declared) so the code
compiles under strict mode.

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