diff --git a/examples/react/offline-transactions/README.md b/examples/react/offline-transactions/README.md
index 90cba4aac..fd5bbf215 100644
--- a/examples/react/offline-transactions/README.md
+++ b/examples/react/offline-transactions/README.md
@@ -1,72 +1,34 @@
-# Welcome to TanStack.com!
+# Offline Transactions Example
-This site is built with TanStack Router!
+A todo app demonstrating `@tanstack/offline-transactions` with three different browser storage backends:
-- [TanStack Router Docs](https://tanstack.com/router)
+- **IndexedDB** — persistent structured storage via `IndexedDBAdapter`
+- **localStorage** — simple key-value fallback via `LocalStorageAdapter`
+- **wa-sqlite OPFS** — full SQLite database in the browser via `@tanstack/browser-db-sqlite-persistence`
-It's deployed automagically with Netlify!
+The app uses TanStack Start in SPA mode with an in-memory server-side todo store. The server simulates network delays and random failures to demonstrate offline resilience.
-- [Netlify](https://netlify.com/)
+## How to run
-## Development
-
-From your terminal:
+From the root of the repository:
```sh
pnpm install
-pnpm dev
-```
-
-This starts your app in development mode, rebuilding assets on file changes.
-
-## Editing and previewing the docs of TanStack projects locally
-
-The documentations for all TanStack projects except for `React Charts` are hosted on [https://tanstack.com](https://tanstack.com), powered by this TanStack Router app.
-In production, the markdown doc pages are fetched from the GitHub repos of the projects, but in development they are read from the local file system.
-
-Follow these steps if you want to edit the doc pages of a project (in these steps we'll assume it's [`TanStack/form`](https://github.com/tanstack/form)) and preview them locally :
-
-1. Create a new directory called `tanstack`.
-
-```sh
-mkdir tanstack
-```
-
-2. Enter the directory and clone this repo and the repo of the project there.
-
-```sh
-cd tanstack
-git clone git@github.com:TanStack/tanstack.com.git
-git clone git@github.com:TanStack/form.git
+pnpm build
```
-> [!NOTE]
-> Your `tanstack` directory should look like this:
->
-> ```
-> tanstack/
-> |
-> +-- form/
-> |
-> +-- tanstack.com/
-> ```
-
-> [!WARNING]
-> Make sure the name of the directory in your local file system matches the name of the project's repo. For example, `tanstack/form` must be cloned into `form` (this is the default) instead of `some-other-name`, because that way, the doc pages won't be found.
-
-3. Enter the `tanstack/tanstack.com` directory, install the dependencies and run the app in dev mode:
+Then from this directory:
```sh
-cd tanstack.com
-pnpm i
-# The app will run on https://localhost:3000 by default
pnpm dev
```
-4. Now you can visit http://localhost:3000/form/latest/docs/overview in the browser and see the changes you make in `tanstack/form/docs`.
+The app runs at http://localhost:3000.
-> [!NOTE]
-> The updated pages need to be manually reloaded in the browser.
+## What it demonstrates
-> [!WARNING]
-> You will need to update the `docs/config.json` file (in the project's repo) if you add a new doc page!
+- **Outbox pattern** — mutations are persisted locally before syncing to the server
+- **Automatic retry** — failed operations retry with exponential backoff when connectivity returns
+- **Multi-tab coordination** — leader election ensures only one tab manages offline storage
+- **Optimistic updates** — UI updates immediately while mutations sync in the background
+- **Collection-level persistence** (wa-sqlite route) — data stored in a real SQLite database in the browser via OPFS, surviving page reloads without server sync
diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json
index ab18c6adb..eabdbfbb8 100644
--- a/examples/react/offline-transactions/package.json
+++ b/examples/react/offline-transactions/package.json
@@ -5,8 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite dev",
- "build": "vite build && tsc --noEmit",
- "start": "node .output/server/index.mjs"
+ "build": "vite build && tsc --noEmit"
},
"dependencies": {
"@tanstack/browser-db-sqlite-persistence": "^0.1.5",
@@ -20,7 +19,6 @@
"@tanstack/react-start": "^1.159.5",
"react": "^19.2.4",
"react-dom": "^19.2.4",
- "tailwind-merge": "^2.6.1",
"zod": "^3.25.76"
},
"devDependencies": {
diff --git a/examples/react/offline-transactions/src/components/DefaultCatchBoundary.tsx b/examples/react/offline-transactions/src/components/DefaultCatchBoundary.tsx
deleted file mode 100644
index ae1b64f67..000000000
--- a/examples/react/offline-transactions/src/components/DefaultCatchBoundary.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-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 (
-
-
-
- {
- router.invalidate()
- }}
- className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
- >
- Try Again
-
- {isRoot ? (
-
- Home
-
- ) : (
- {
- e.preventDefault()
- window.history.back()
- }}
- >
- Go Back
-
- )}
-
-
- )
-}
diff --git a/examples/react/offline-transactions/src/components/NotFound.tsx b/examples/react/offline-transactions/src/components/NotFound.tsx
deleted file mode 100644
index 7b54fa568..000000000
--- a/examples/react/offline-transactions/src/components/NotFound.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Link } from '@tanstack/react-router'
-
-export function NotFound({ children }: { children?: any }) {
- return (
-
-
- {children ||
The page you are looking for does not exist.
}
-
-
- window.history.back()}
- className="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm"
- >
- Go back
-
-
- Start Over
-
-
-
- )
-}
diff --git a/examples/react/offline-transactions/src/routeTree.gen.ts b/examples/react/offline-transactions/src/routeTree.gen.ts
index c59e9b6ff..579e1a96e 100644
--- a/examples/react/offline-transactions/src/routeTree.gen.ts
+++ b/examples/react/offline-transactions/src/routeTree.gen.ts
@@ -13,6 +13,8 @@ import { Route as WaSqliteRouteImport } from './routes/wa-sqlite'
import { Route as LocalstorageRouteImport } from './routes/localstorage'
import { Route as IndexeddbRouteImport } from './routes/indexeddb'
import { Route as IndexRouteImport } from './routes/index'
+import { Route as ApiTodosRouteImport } from './routes/api/todos'
+import { Route as ApiTodosTodoIdRouteImport } from './routes/api/todos.$todoId'
const WaSqliteRoute = WaSqliteRouteImport.update({
id: '/wa-sqlite',
@@ -34,18 +36,32 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
+const ApiTodosRoute = ApiTodosRouteImport.update({
+ id: '/api/todos',
+ path: '/api/todos',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const ApiTodosTodoIdRoute = ApiTodosTodoIdRouteImport.update({
+ id: '/$todoId',
+ path: '/$todoId',
+ getParentRoute: () => ApiTodosRoute,
+} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/indexeddb': typeof IndexeddbRoute
'/localstorage': typeof LocalstorageRoute
'/wa-sqlite': typeof WaSqliteRoute
+ '/api/todos': typeof ApiTodosRouteWithChildren
+ '/api/todos/$todoId': typeof ApiTodosTodoIdRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/indexeddb': typeof IndexeddbRoute
'/localstorage': typeof LocalstorageRoute
'/wa-sqlite': typeof WaSqliteRoute
+ '/api/todos': typeof ApiTodosRouteWithChildren
+ '/api/todos/$todoId': typeof ApiTodosTodoIdRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -53,13 +69,34 @@ export interface FileRoutesById {
'/indexeddb': typeof IndexeddbRoute
'/localstorage': typeof LocalstorageRoute
'/wa-sqlite': typeof WaSqliteRoute
+ '/api/todos': typeof ApiTodosRouteWithChildren
+ '/api/todos/$todoId': typeof ApiTodosTodoIdRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
- fullPaths: '/' | '/indexeddb' | '/localstorage' | '/wa-sqlite'
+ fullPaths:
+ | '/'
+ | '/indexeddb'
+ | '/localstorage'
+ | '/wa-sqlite'
+ | '/api/todos'
+ | '/api/todos/$todoId'
fileRoutesByTo: FileRoutesByTo
- to: '/' | '/indexeddb' | '/localstorage' | '/wa-sqlite'
- id: '__root__' | '/' | '/indexeddb' | '/localstorage' | '/wa-sqlite'
+ to:
+ | '/'
+ | '/indexeddb'
+ | '/localstorage'
+ | '/wa-sqlite'
+ | '/api/todos'
+ | '/api/todos/$todoId'
+ id:
+ | '__root__'
+ | '/'
+ | '/indexeddb'
+ | '/localstorage'
+ | '/wa-sqlite'
+ | '/api/todos'
+ | '/api/todos/$todoId'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
@@ -67,6 +104,7 @@ export interface RootRouteChildren {
IndexeddbRoute: typeof IndexeddbRoute
LocalstorageRoute: typeof LocalstorageRoute
WaSqliteRoute: typeof WaSqliteRoute
+ ApiTodosRoute: typeof ApiTodosRouteWithChildren
}
declare module '@tanstack/react-router' {
@@ -99,14 +137,41 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
+ '/api/todos': {
+ id: '/api/todos'
+ path: '/api/todos'
+ fullPath: '/api/todos'
+ preLoaderRoute: typeof ApiTodosRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/api/todos/$todoId': {
+ id: '/api/todos/$todoId'
+ path: '/$todoId'
+ fullPath: '/api/todos/$todoId'
+ preLoaderRoute: typeof ApiTodosTodoIdRouteImport
+ parentRoute: typeof ApiTodosRoute
+ }
}
}
+interface ApiTodosRouteChildren {
+ ApiTodosTodoIdRoute: typeof ApiTodosTodoIdRoute
+}
+
+const ApiTodosRouteChildren: ApiTodosRouteChildren = {
+ ApiTodosTodoIdRoute: ApiTodosTodoIdRoute,
+}
+
+const ApiTodosRouteWithChildren = ApiTodosRoute._addFileChildren(
+ ApiTodosRouteChildren,
+)
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
IndexeddbRoute: IndexeddbRoute,
LocalstorageRoute: LocalstorageRoute,
WaSqliteRoute: WaSqliteRoute,
+ ApiTodosRoute: ApiTodosRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/examples/react/offline-transactions/src/router.tsx b/examples/react/offline-transactions/src/router.tsx
index e9f12d870..28da0bb4d 100644
--- a/examples/react/offline-transactions/src/router.tsx
+++ b/examples/react/offline-transactions/src/router.tsx
@@ -1,14 +1,10 @@
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
-import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
-import { NotFound } from './components/NotFound'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
defaultPreload: `intent`,
- defaultErrorComponent: DefaultCatchBoundary,
- defaultNotFoundComponent: () => ,
scrollRestoration: true,
})
diff --git a/examples/react/offline-transactions/src/routes/__root.tsx b/examples/react/offline-transactions/src/routes/__root.tsx
index fb72e0806..fb0fb299a 100644
--- a/examples/react/offline-transactions/src/routes/__root.tsx
+++ b/examples/react/offline-transactions/src/routes/__root.tsx
@@ -8,10 +8,7 @@ import {
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import { QueryClientProvider } from '@tanstack/react-query'
import * as React from 'react'
-import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
-import { NotFound } from '~/components/NotFound'
import appCss from '~/styles/app.css?url'
-import { seo } from '~/utils/seo'
import { queryClient } from '~/utils/queryClient'
export const Route = createRootRoute({
@@ -24,42 +21,9 @@ export const Route = createRootRoute({
name: `viewport`,
content: `width=device-width, initial-scale=1`,
},
- ...seo({
- title: `TanStack Start | Type-Safe, Client-First, Full-Stack React Framework`,
- description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
- }),
- ],
- links: [
- { rel: `stylesheet`, href: appCss },
- {
- rel: `apple-touch-icon`,
- sizes: `180x180`,
- href: `/apple-touch-icon.png`,
- },
- {
- rel: `icon`,
- type: `image/png`,
- sizes: `32x32`,
- href: `/favicon-32x32.png`,
- },
- {
- rel: `icon`,
- type: `image/png`,
- sizes: `16x16`,
- href: `/favicon-16x16.png`,
- },
- { rel: `manifest`, href: `/site.webmanifest`, color: `#fffff` },
- { rel: `icon`, href: `/favicon.ico` },
- ],
- scripts: [
- {
- src: `/customScript.js`,
- type: `text/javascript`,
- },
],
+ links: [{ rel: `stylesheet`, href: appCss }],
}),
- errorComponent: DefaultCatchBoundary,
- notFoundComponent: () => ,
shellComponent: RootDocument,
})
diff --git a/examples/react/offline-transactions/src/routes/api/todos.$todoId.ts b/examples/react/offline-transactions/src/routes/api/todos.$todoId.ts
index 21b293645..47fcc7d89 100644
--- a/examples/react/offline-transactions/src/routes/api/todos.$todoId.ts
+++ b/examples/react/offline-transactions/src/routes/api/todos.$todoId.ts
@@ -1,80 +1,84 @@
-import { createServerFileRoute } from '@tanstack/react-start/server'
+import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import type { TodoUpdate } from '~/utils/todos'
import { todoService } from '~/utils/todos'
-export const ServerRoute = createServerFileRoute(`/api/todos/$todoId`).methods({
- GET: async ({ params, request }) => {
- console.info(`GET /api/todos/${params.todoId} @`, request.url)
+export const Route = createFileRoute(`/api/todos/$todoId`)({
+ server: {
+ handlers: {
+ GET: async ({ params, request }) => {
+ console.info(`GET /api/todos/${params.todoId} @`, request.url)
- try {
- const todo = await todoService.withDelay(() => {
- todoService.simulateFailure(0.1)
- return todoService.getById(params.todoId)
- })
+ try {
+ const todo = await todoService.withDelay(() => {
+ todoService.simulateFailure(0.1)
+ return todoService.getById(params.todoId)
+ })
- if (!todo) {
- return json({ error: `Todo not found` }, { status: 404 })
- }
+ if (!todo) {
+ return json({ error: `Todo not found` }, { status: 404 })
+ }
- return json(todo)
- } catch (error) {
- console.error(`Error fetching todo:`, error)
- return json({ error: `Failed to fetch todo` }, { status: 500 })
- }
- },
+ return json(todo)
+ } catch (error) {
+ console.error(`Error fetching todo:`, error)
+ return json({ error: `Failed to fetch todo` }, { status: 500 })
+ }
+ },
- PUT: async ({ params, request }) => {
- console.info(`PUT /api/todos/${params.todoId} @`, request.url)
+ PUT: async ({ params, request }) => {
+ console.info(`PUT /api/todos/${params.todoId} @`, request.url)
- try {
- const body = (await request.json()) as TodoUpdate
+ try {
+ const body = (await request.json()) as TodoUpdate
- const todo = await todoService.withDelay(() => {
- todoService.simulateFailure(0.15)
- return todoService.update(params.todoId, body)
- })
+ const todo = await todoService.withDelay(() => {
+ todoService.simulateFailure(0.15)
+ return todoService.update(params.todoId, body)
+ })
- if (!todo) {
- return json({ error: `Todo not found` }, { status: 404 })
- }
+ if (!todo) {
+ return json({ error: `Todo not found` }, { status: 404 })
+ }
- return json(todo)
- } catch (error) {
- console.error(`Error updating todo:`, error)
- if (error instanceof Error && error.message.includes(`Simulated`)) {
- return json(
- { error: `Network error - please try again` },
- { status: 503 },
- )
- }
- return json({ error: `Failed to update todo` }, { status: 500 })
- }
- },
+ return json(todo)
+ } catch (error) {
+ console.error(`Error updating todo:`, error)
+ if (error instanceof Error && error.message.includes(`Simulated`)) {
+ return json(
+ { error: `Network error - please try again` },
+ { status: 503 },
+ )
+ }
+ return json({ error: `Failed to update todo` }, { status: 500 })
+ }
+ },
- DELETE: async ({ params, request }) => {
- console.info(`DELETE /api/todos/${params.todoId} @`, request.url)
+ DELETE: async ({ params, request }) => {
+ console.info(`DELETE /api/todos/${params.todoId} @`, request.url)
- try {
- const success = await todoService.withDelay(() => {
- todoService.simulateFailure(0.15)
- return todoService.delete(params.todoId)
- })
+ try {
+ const success = await todoService.withDelay(() => {
+ todoService.simulateFailure(0.15)
+ return todoService.delete(params.todoId)
+ })
- if (!success) {
- return json({ error: `Todo not found` }, { status: 404 })
- }
+ if (!success) {
+ return json({ error: `Todo not found` }, { status: 404 })
+ }
- return json({ success: true })
- } catch (error) {
- console.error(`Error deleting todo:`, error)
- if (error instanceof Error && error.message.includes(`Simulated`)) {
- return json(
- { error: `Network error - please try again` },
- { status: 503 },
- )
- }
- return json({ error: `Failed to delete todo` }, { status: 500 })
- }
+ return json({ success: true })
+ } catch (error) {
+ console.error(`Error deleting todo:`, error)
+ if (error instanceof Error && error.message.includes(`Simulated`)) {
+ return json(
+ { error: `Network error - please try again` },
+ { status: 503 },
+ )
+ }
+ return json({ error: `Failed to delete todo` }, { status: 500 })
+ }
+ },
+ },
},
})
diff --git a/examples/react/offline-transactions/src/routes/api/todos.ts b/examples/react/offline-transactions/src/routes/api/todos.ts
index 8b6d1f8d7..e5fb5691a 100644
--- a/examples/react/offline-transactions/src/routes/api/todos.ts
+++ b/examples/react/offline-transactions/src/routes/api/todos.ts
@@ -1,52 +1,56 @@
-import { createServerFileRoute } from '@tanstack/react-start/server'
+import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import type { TodoInput } from '~/utils/todos'
import { todoService } from '~/utils/todos'
-export const ServerRoute = createServerFileRoute(`/api/todos`).methods({
- GET: async ({ request }) => {
- console.info(`GET /api/todos @`, request.url)
-
- try {
- const todos = await todoService.withDelay(() => {
- // Occasionally simulate failure for demo
- todoService.simulateFailure(0.1)
- return todoService.getAll()
- })
-
- return json(todos)
- } catch (error) {
- console.error(`Error fetching todos:`, error)
- return json({ error: `Failed to fetch todos` }, { status: 500 })
- }
- },
-
- POST: async ({ request }) => {
- console.info(`POST /api/todos @`, request.url)
-
- try {
- const body = (await request.json()) as TodoInput
-
- if (!body.text || body.text.trim() === ``) {
- return json({ error: `Todo text is required` }, { status: 400 })
- }
-
- const todo = await todoService.withDelay(() => {
- // Occasionally simulate failure for demo
- todoService.simulateFailure(0.15)
- return todoService.create(body)
- })
-
- return json(todo, { status: 201 })
- } catch (error) {
- console.error(`Error creating todo:`, error)
- if (error instanceof Error && error.message.includes(`Simulated`)) {
- return json(
- { error: `Network error - please try again` },
- { status: 503 },
- )
- }
- return json({ error: `Failed to create todo` }, { status: 500 })
- }
+export const Route = createFileRoute(`/api/todos`)({
+ server: {
+ handlers: {
+ GET: async ({ request }) => {
+ console.info(`GET /api/todos @`, request.url)
+
+ try {
+ const todos = await todoService.withDelay(() => {
+ // Occasionally simulate failure for demo
+ todoService.simulateFailure(0.1)
+ return todoService.getAll()
+ })
+
+ return json(todos)
+ } catch (error) {
+ console.error(`Error fetching todos:`, error)
+ return json({ error: `Failed to fetch todos` }, { status: 500 })
+ }
+ },
+
+ POST: async ({ request }) => {
+ console.info(`POST /api/todos @`, request.url)
+
+ try {
+ const body = (await request.json()) as TodoInput
+
+ if (!body.text || body.text.trim() === ``) {
+ return json({ error: `Todo text is required` }, { status: 400 })
+ }
+
+ const todo = await todoService.withDelay(() => {
+ // Occasionally simulate failure for demo
+ todoService.simulateFailure(0.15)
+ return todoService.create(body)
+ })
+
+ return json(todo, { status: 201 })
+ } catch (error) {
+ console.error(`Error creating todo:`, error)
+ if (error instanceof Error && error.message.includes(`Simulated`)) {
+ return json(
+ { error: `Network error - please try again` },
+ { status: 503 },
+ )
+ }
+ return json({ error: `Failed to create todo` }, { status: 500 })
+ }
+ },
+ },
},
})
diff --git a/examples/react/offline-transactions/src/routes/api/users.$userId.ts b/examples/react/offline-transactions/src/routes/api/users.$userId.ts
deleted file mode 100644
index 19426fbbf..000000000
--- a/examples/react/offline-transactions/src/routes/api/users.$userId.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { createServerFileRoute } from '@tanstack/react-start/server'
-import { json } from '@tanstack/react-start'
-import type { User } from '~/utils/users'
-
-export const ServerRoute = createServerFileRoute(`/api/users/$userId`).methods({
- GET: async ({ params, request }) => {
- console.info(`Fetching users by id=${params.userId}... @`, request.url)
- try {
- const res = await fetch(
- `https://jsonplaceholder.typicode.com/users/` + params.userId,
- )
- if (!res.ok) {
- throw new Error(`Failed to fetch user`)
- }
-
- const user = (await res.json()) as User
-
- return json({
- id: user.id,
- name: user.name,
- email: user.email,
- })
- } catch (e) {
- console.error(e)
- return json({ error: `User not found` }, { status: 404 })
- }
- },
-})
diff --git a/examples/react/offline-transactions/src/routes/api/users.ts b/examples/react/offline-transactions/src/routes/api/users.ts
deleted file mode 100644
index b627c2f1f..000000000
--- a/examples/react/offline-transactions/src/routes/api/users.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- createServerFileRoute,
- getRequestHeaders,
-} from '@tanstack/react-start/server'
-import { createMiddleware, json } from '@tanstack/react-start'
-import type { User } from '~/utils/users'
-
-const userLoggerMiddleware = createMiddleware({ type: `request` }).server(
- async ({ next, _request }) => {
- console.info(`In: /users`)
- console.info(`Request Headers:`, getRequestHeaders())
- const result = await next()
- result.response.headers.set(`x-users`, `true`)
- console.info(`Out: /users`)
- return result
- },
-)
-
-const testParentMiddleware = createMiddleware({ type: `request` }).server(
- async ({ next, _request }) => {
- console.info(`In: testParentMiddleware`)
- const result = await next()
- result.response.headers.set(`x-test-parent`, `true`)
- console.info(`Out: testParentMiddleware`)
- return result
- },
-)
-
-const testMiddleware = createMiddleware({ type: `request` })
- .middleware([testParentMiddleware])
- .server(async ({ next, _request }) => {
- console.info(`In: testMiddleware`)
- const result = await next()
- result.response.headers.set(`x-test`, `true`)
-
- // if (Math.random() > 0.5) {
- // throw new Response(null, {
- // status: 302,
- // headers: { Location: 'https://www.google.com' },
- // })
- // }
-
- console.info(`Out: testMiddleware`)
- return result
- })
-
-export const ServerRoute = createServerFileRoute(`/api/users`)
- .middleware([testMiddleware, userLoggerMiddleware, testParentMiddleware])
- .methods({
- GET: async ({ request }) => {
- console.info(`GET /api/users @`, request.url)
- console.info(`Fetching users... @`, request.url)
- const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
- if (!res.ok) {
- throw new Error(`Failed to fetch users`)
- }
-
- const data = (await res.json()) as Array
-
- const list = data.slice(0, 10)
-
- return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email })))
- },
- })
diff --git a/examples/react/offline-transactions/src/utils/loggingMiddleware.tsx b/examples/react/offline-transactions/src/utils/loggingMiddleware.tsx
deleted file mode 100644
index 336587007..000000000
--- a/examples/react/offline-transactions/src/utils/loggingMiddleware.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { createMiddleware } from '@tanstack/react-start'
-
-const preLogMiddleware = createMiddleware({ type: `function` })
- .client(async (ctx) => {
- const clientTime = new Date()
-
- return ctx.next({
- context: {
- clientTime,
- },
- sendContext: {
- clientTime,
- },
- })
- })
- .server(async (ctx) => {
- const serverTime = new Date()
-
- return ctx.next({
- sendContext: {
- serverTime,
- durationToServer:
- serverTime.getTime() - ctx.context.clientTime.getTime(),
- },
- })
- })
-
-export const logMiddleware = createMiddleware({ type: `function` })
- .middleware([preLogMiddleware])
- .client(async (ctx) => {
- const res = await ctx.next()
-
- const now = new Date()
- console.log(`Client Req/Res:`, {
- duration: now.getTime() - res.context.clientTime.getTime(),
- durationToServer: res.context.durationToServer,
- durationFromServer: now.getTime() - res.context.serverTime.getTime(),
- })
-
- return res
- })
diff --git a/examples/react/offline-transactions/src/utils/seo.ts b/examples/react/offline-transactions/src/utils/seo.ts
deleted file mode 100644
index bbbdd34ad..000000000
--- a/examples/react/offline-transactions/src/utils/seo.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-export const seo = ({
- title,
- description,
- keywords,
- image,
-}: {
- title: string
- description?: string
- image?: string
- keywords?: string
-}) => {
- const tags = [
- { title },
- { name: `description`, content: description },
- { name: `keywords`, content: keywords },
- { name: `twitter:title`, content: title },
- { name: `twitter:description`, content: description },
- { name: `twitter:creator`, content: `@tannerlinsley` },
- { name: `twitter:site`, content: `@tannerlinsley` },
- { name: `og:type`, content: `website` },
- { name: `og:title`, content: title },
- { name: `og:description`, content: description },
- ...(image
- ? [
- { name: `twitter:image`, content: image },
- { name: `twitter:card`, content: `summary_large_image` },
- { name: `og:image`, content: image },
- ]
- : []),
- ]
-
- return tags
-}
diff --git a/examples/react/offline-transactions/vite.config.ts b/examples/react/offline-transactions/vite.config.ts
index 42992cae6..bebbe5a43 100644
--- a/examples/react/offline-transactions/vite.config.ts
+++ b/examples/react/offline-transactions/vite.config.ts
@@ -129,8 +129,7 @@ export default defineConfig({
projects: [`./tsconfig.json`],
}),
tanstackStart({
- customViteReactPlugin: true,
- mode: `spa`, // SPA mode for client-side only offline features
+ spa: { enabled: true },
}),
viteReact(),
tailwindcss(),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dba49a81d..1f345f749 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -514,9 +514,6 @@ importers:
react-dom:
specifier: ^19.2.4
version: 19.2.4(react@19.2.4)
- tailwind-merge:
- specifier: ^2.6.1
- version: 2.6.1
zod:
specifier: ^3.25.76
version: 3.25.76
@@ -11920,9 +11917,6 @@ packages:
resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
engines: {node: ^14.18.0 || >=16.0.0}
- tailwind-merge@2.6.1:
- resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==}
-
tailwindcss@4.1.18:
resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
@@ -25583,8 +25577,6 @@ snapshots:
dependencies:
'@pkgr/core': 0.2.9
- tailwind-merge@2.6.1: {}
-
tailwindcss@4.1.18: {}
tapable@2.2.3: {}