## File: README.md

| ## TanStack Router

A modern router designed for type safety, data‑driven navigation, and seamless developer experience.

- End‑to-end type safety (routes, params, loaders)
- Schema‑driven search params with validation
- Built‑in caching, prefetching & invalidation
- Nested layouts, transitions & error boundaries

### [Read the Router Docs →](https://tanstack.com/router) | ## TanStack Start

A full‑stack framework built on Router, designed for server rendering, streaming, and production‑ready deployments.

- Full‑document SSR & streaming
- Server functions & end‑to‑end type safety
- Deployment‑ready bundling & builds
- All the power of TanStack Router, plus full‑stack features

### [Read the Start Docs →](https://tanstack.com/start) |
| --- | --- |










## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/router/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners



|  |  |  |
| --- | --- | --- |
|  |  |  |
|  |  |  |





## Explore the TanStack Ecosystem

- [**TanStack Config**](https://github.com/tanstack/config) – Tooling for JS/TS packages
- [**TanStack DB**](https://github.com/tanstack/db) – Reactive sync client store
- [**TanStack DevTools**](https://github.com/tanstack/devtools) – Unified devtools panel
- [**TanStack Form**](https://github.com/tanstack/form) – Type‑safe form state
- [**TanStack Pacer**](https://github.com/tanstack/pacer) – Debouncing, throttling, batching 
- [**TanStack Query**](https://github.com/tanstack/query) – Async state & caching
- [**TanStack Ranger**](https://github.com/tanstack/ranger) – Range & slider primitives
- [**TanStack Store**](https://github.com/tanstack/store) – Reactive data store
- [**TanStack Table**](https://github.com/tanstack/table) – Headless datagrids
- [**TanStack Virtual**](https://github.com/tanstack/virtual) – Virtualized rendering

… and more at [**TanStack.com »**](https://tanstack.com)

---

## File: benchmarks/ssr/README.md

# SSR Benchmarks

Cross-framework SSR request-loop benchmarks for:

- `@tanstack/react-start`
- `@tanstack/solid-start`
- `@tanstack/vue-start`

Each benchmark builds a Start app with file-based routes and runs Vitest benches against the built server handler.

## Layout

- `react/` - React Start baseline benchmark + Vitest config
- `solid/` - Solid Start baseline benchmark + Vitest config
- `vue/` - Vue Start baseline benchmark + Vitest config
- `vitest.react.config.ts`, `vitest.solid.config.ts`, `vitest.vue.config.ts` - per-framework aggregate configs that run the baseline first, then scenario projects
- `scenarios/<scenario>/<framework>/` - isolated scenario apps

Scenario app layout:

```text
scenarios/<scenario>/<framework>/
  vite.config.ts
  speed.bench.ts
  tsconfig.json
  src/
    router.tsx
    routes/
    routeTree.gen.ts
```

Each scenario uses one app per framework instead of sharing routes in the baseline app. This keeps route-tree size, middleware, Start options, and generated route trees isolated so one scenario cannot shift another scenario's numbers. The existing baseline apps and bench names stay stable for CodSpeed continuity.

## Scenario Responsibilities

Each scenario isolates one Start server-side responsibility so benchmark changes can be attributed to a specific feature area.

| Scenario                                 | Start server-side responsibility                                                                                                                                                                                          |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `react/`, `solid/`, `vue/` baseline apps | Document SSR for nested file routes, route matching, search parsing, and full-page HTML response generation.                                                                                                              |
| `assets`                                 | Per-request asset pipeline work: CSS inlining, CDN asset URL transforms with uncached manifest resolution, and response `Link` header collection for early hints.                                                         |
| `before-load`                            | Nested `beforeLoad` execution: sequential per-match context building, context merging across matches, and context consumption by loaders during document SSR.                                                             |
| `control-flow`                           | Loader-thrown `redirect`, `notFound`, plain errors, unmatched routes, and route `headers()` emission, including HTTP status selection, redirect `location` headers, route error boundaries, and not-found HTML rendering. |
| `global-middleware`                      | Global `createStart` middleware registration: request middleware wrapping document SSR, server-function, and server-route requests, plus global function middleware on server functions.                                  |
| `head`                                   | Nested route `head` evaluation, title/meta/link serialization, and head-entry deduplication during SSR.                                                                                                                   |
| `loaders`                                | Nested route loader execution, loader deps from search params, router context reads, and dehydrated loader payload generation.                                                                                            |
| `rewrites`                               | Composed location rewrites from router `basepath` plus locale input/output rewrites; unprefixed URLs redirect to `/app/*` when the basepath output rewrite canonicalizes them.                                            |
| `serialization`                          | Dehydration and RPC serialization of rich non-plain-JSON types: seroval native types in loader payloads, custom serialization adapters, and shallow-error serialization.                                                  |
| `selective-ssr`                          | Route-level `ssr` modes: rendered server HTML with `ssr: true`, dehydrated data without HTML with `ssr: 'data-only'`, and client-only omission with `ssr: false`.                                                         |
| `server-fn-transport`                    | Non-JSON server-function transport: multipart/FormData request decoding, raw `Response` returns, and `RawStream` streamed responses through the binary frame protocol.                                                    |
| `server-fns`                             | `createServerFn` GET/POST request handling, thrown redirect/notFound results, sendContext serialization, SSR-time direct calls, middleware context, input validation, and URL discovery.                                  |
| `server-routes`                          | File route `server.handlers` dispatch for parameterized JSON API routes without document HTML rendering.                                                                                                                  |
| `server-routes-middleware`               | Server route request middleware chains, middleware-provided context merging, and handler access to accumulated context.                                                                                                   |
| `streaming`                              | Deferred loader data, `Await`/Suspense fallback output, larger streamed HTML body scanning, streamed HTML ordering, and later dehydration payload emission.                                                               |

## Run

Run all benchmarks through Nx so dependency builds are part of the graph:

```bash
pnpm nx run @benchmarks/ssr:test:perf --outputStyle=stream --skipRemoteCache
```

Run framework-specific benchmarks:

```bash
pnpm nx run @benchmarks/ssr:test:perf:react --outputStyle=stream --skipRemoteCache
pnpm nx run @benchmarks/ssr:test:perf:solid --outputStyle=stream --skipRemoteCache
pnpm nx run @benchmarks/ssr:test:perf:vue --outputStyle=stream --skipRemoteCache
```

Build framework-specific benchmark apps:

```bash
pnpm nx run @benchmarks/ssr:build:react --outputStyle=stream --skipRemoteCache
pnpm nx run @benchmarks/ssr:build:solid --outputStyle=stream --skipRemoteCache
pnpm nx run @benchmarks/ssr:build:vue --outputStyle=stream --skipRemoteCache
```

Typecheck benchmark sources:

```bash
pnpm nx run @benchmarks/ssr:test:types --outputStyle=stream --skipRemoteCache
```

Run one scenario app manually through Nx:

```bash
pnpm nx run @benchmarks/ssr-<scenario>-<framework>:build:ssr --outputStyle=stream --skipRemoteCache
pnpm nx run @benchmarks/ssr-<scenario>-<framework>:test:types:ssr --outputStyle=stream --skipRemoteCache
```

Use `react`, `solid`, or `vue` for `<framework>`. The baseline projects use `@benchmarks/ssr-<framework>` without a scenario segment.

## Request Conventions

- Document GET loops use `accept: text/html`, matching the baseline request shape.
- Server-function loops must include `sec-fetch-site: same-origin` so the default CSRF middleware accepts the request.
- Loops that expect non-200 responses pass a custom `validateResponse` to `runRequestLoop`.
- Bench loops must build deterministic requests from the seeded random helper and consume response bodies through `runRequestLoop` or `runSsrRequestLoop`.

---

## File: benchmarks/client-nav/README.md

# Client Navigation Benchmarks

Cross-framework client-side CPU benchmarks for:

- `@tanstack/react-router`
- `@tanstack/solid-router`
- `@tanstack/vue-router`

The benchmarks run in jsdom against production builds of real apps, and are
tracked in CI by CodSpeed (simulation mode).

> **Scope:** these benchmarks cover the standalone client router only. The
> client side of TanStack Start (hydration of a server-rendered document,
> streamed payload consumption, server-function calls from the client, ...) is
> not covered here; the server side of Start is covered by `benchmarks/ssr`.

## Layout

- `react/`, `solid/`, `vue/` - baseline benchmark (mixed navigation loop) + Vitest config
- `vitest.react.config.ts`, `vitest.solid.config.ts`, `vitest.vue.config.ts` - per-framework aggregate configs that run the baseline first, then scenario projects
- `scenarios/harness.ts` - shared scenario runner (mount, link-click steps, `onRendered` synchronization)
- `scenarios/<scenario>/shared.ts` - framework-agnostic scenario definition (workload data, step sequence, assertions, bench options)
- `scenarios/<scenario>/<framework>/` - isolated scenario apps

Scenario app layout:

```text
scenarios/<scenario>/<framework>/
  vite.config.ts
  speed.bench.ts
  speed.flame.ts
  setup.ts
  project.json
  tsconfig.json
  src/
    main.tsx
    routeTree.gen.ts
    routes/
```

Scenario apps use file-based routing (`@tanstack/router-plugin`) with a
generated `routeTree.gen.ts`, like a regular user app. Each scenario uses one
app per framework instead of sharing routes in the baseline app. This keeps
route-tree size and router options isolated so one scenario cannot shift
another scenario's numbers. The existing baseline apps and bench names stay
stable for CodSpeed continuity.

## Scenario Responsibilities

Each scenario isolates one client-side responsibility so benchmark changes can
be attributed to a specific feature area.

| Scenario                                 | Client-side responsibility                                                                                                                                                                                                                                                                                                           |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `react/`, `solid/`, `vue/` baseline apps | Mixed navigation loop: path params, search params, route context, and `useParams`/`useSearch`/`useLoaderData` selector subscriptions.                                                                                                                                                                                                |
| `async-pipeline`                         | The router's async pipeline via counted 0ms timer hops: async loaders (transition-held navigation), async `beforeLoad` context, and parallel nested async loaders. Component-level `Await`/Suspense is excluded: React 19 throttles Suspense reveals by ~300ms wall-clock, which is inherently non-deterministic to benchmark.       |
| `control-flow`                           | Loader-thrown `redirect` (including a 2-hop chain), `notFound()` with `notFoundComponent`, loader errors with `errorComponent`, and boundary reset on recovery navigation.                                                                                                                                                           |
| `head`                                   | `HeadContent` per-navigation work: nested route `head()` evaluation, title/meta/link dedupe across matches, and head tag DOM updates during navigation.                                                                                                                                                                              |
| `history`                                | History push/replace/back/forward traversal, location masking, registered-but-never-blocking `useBlocker`, and `useCanGoBack`/`useLocation` subscriptions.                                                                                                                                                                           |
| `links`                                  | Per-navigation cost of ~200 mounted `<Link>`s: link prop building, active-state recompute across `activeOptions` variants, `activeProps` swaps, and `useMatchRoute` probes (the `MatchRoute` component is avoided: vue-router's implementation leaks one subscription per render).                                                   |
| `loaders`                                | Client loader dispatch: always-stale re-runs (`staleTime: 0`), cached revisits (re-run once per lap by the `invalidate` step), `loaderDeps`-keyed caching, `router.invalidate()`, and `useLoaderData` selectors.                                                                                                                     |
| `mount`                                  | Cold start: `createRouter` (route-tree processing) + first render + initial `router.load()` + unmount, with a fresh router per mount. The only scenario measuring router creation.                                                                                                                                                   |
| `nested-params`                          | Deep nesting (8 dynamic levels): per-level `params.parse`/`stringify`, `beforeLoad` context accumulation across matches, and per-level `useParams`/`useRouteContext` subscriptions. Param values include characters requiring percent-encoding (as do `route-tree-scale`'s), so segment encode/decode paths run on every navigation. |
| `preload`                                | Intent preloading from hover events, programmatic `router.preloadRoute`, deterministic preload cache behavior (`defaultPreloadStaleTime: 0`), and commit-time cache maintenance.                                                                                                                                                     |
| `rewrites`                               | Composed client-side location rewrites: router `basepath` plus a locale input/output rewrite pair, running on every href build and location parse (the client analog of the SSR `rewrites` scenario).                                                                                                                                |
| `route-tree-scale`                       | Route matching and link-target resolution on a wide (~40 route) tree mixing static, dynamic, prefixed-param, splat, pathless-layout, and route-group paths, with `autoCodeSplitting` enabled so navigations also resolve lazy route chunks.                                                                                          |
| `search-params`                          | `validateSearch` execution, search middlewares (`retainSearchParams`/`stripSearchParams`), functional search updaters, structural sharing, and `useSearch` selector subscriptions.                                                                                                                                                   |

## Conventions

- Apps are built with `NODE_ENV=production` (`minify: false`) into `dist/app.js`; benches import the built bundle, so production package builds and production JSX output are measured, not dev transforms.
- Scenarios behave like a real user app: navigation happens through `<Link>` clicks dispatched on real anchor elements (unless a scenario specifically measures the imperative API), the router uses the default browser history, and `scrollRestoration` is enabled.
- Each benchmark iteration advances a fixed, circular sequence of steps; every step awaits the router's `onRendered` event, so render work is included and steps cannot overlap. No two consecutive steps may target the same location, and the sequence ends back on the initial route.
- Setup runs one warm-up lap through the sequence and asserts each step's observable output (e.g. `document.title`), so a scenario that silently stops doing its work fails instead of reporting a fast time.
- Determinism: no wall-clock timers (async work is resolved promises or counted `setTimeout(0)` hops), `staleTime`/`gcTime` only ever `0` or effectively-infinite, no `Math.random`/`Date.now` in the measured loop. Scroll restoration uses `getScrollRestorationKey: (location) => location.pathname` so its cache stays bounded (the default random per-entry key grows one cache entry per push navigation). The `mount` scenario opts out of `scrollRestoration` entirely and destroys its history on unmount — both register page-lifetime globals that would leak across its mount/unmount loop.
- Push-only step laps grow jsdom's session-history entry list over a run (jsdom never truncates entries behind the current index). Per-push cost stays O(1) so timings are unaffected; only the `history` scenario needs — and has — a lap that is depth-stationary.
- Calibration: pick per-iteration step counts so a bench's `vitest bench` run stays roughly between 8 and 30 seconds (long enough to average out, short enough for CI).

## Run

Run all benchmarks through Nx so dependency builds are part of the graph:

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf --outputStyle=stream --skipRemoteCache
```

Run framework-specific benchmarks (baseline + all scenarios):

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf:react --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf:solid --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf:vue --outputStyle=stream --skipRemoteCache
```

Run a single scenario app manually (after building it through Nx):

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav-<scenario>-<framework>:build:client --outputStyle=stream --skipRemoteCache
cd benchmarks/client-nav && NODE_ENV=production vitest bench --config ./scenarios/<scenario>/<framework>/vite.config.ts
```

Run framework-specific flame benchmarks (10 second loop, profiled with `@platformatic/flame`, forced to `NODE_ENV=production`):

```bash
# baseline
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:flame:react --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:flame:solid --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:flame:vue --outputStyle=stream --skipRemoteCache
# scenarios
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav-<scenario>-<framework>:test:flame --outputStyle=stream --skipRemoteCache
```

Typecheck benchmark sources (baseline + scenarios):

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:types --outputStyle=stream --skipRemoteCache
```

---

## File: benchmarks/bundle-size/README.md

# Bundle Size Benchmarks

This workspace contains deterministic bundle-size fixtures for:

- `@tanstack/react-router`
- `@tanstack/solid-router`
- `@tanstack/vue-router`
- `@tanstack/react-start`
- `@tanstack/solid-start`

Each package has `minimal` and `full` scenarios:

- `minimal`: Small route app with `__root` + index route that renders `hello world`
- `full`: Same route shape plus a broad root-level harness that imports/uses the full hooks/components surface
- Start `full` scenarios also exercise `createServerFn`, `createMiddleware`, and `useServerFn`
- Start `deferred-hydration` scenarios match the minimal route shape and wrap the index route content in `Hydrate`

## Design Notes

- Scenarios use file-based routing as the default app style.
- Router scenarios use `@tanstack/router-plugin/vite` with `autoCodeSplitting: true`.
- Start Vite scenarios use `@tanstack/<framework>-start/plugin/vite` with router code-splitting enabled.
- React Start also includes Rsbuild scenarios using `@tanstack/react-start/plugin/rsbuild`.
- Full-surface coverage is manually maintained (no strict export-coverage gate).
- Primary metrics measure all emitted client JS chunks and are reported as raw/gzip/brotli bytes.
- Initial-load JS graph metrics are also recorded as `initialRawBytes`, `initialGzipBytes`, and `initialBrotliBytes` for context.
- Gzip for all emitted client JS is the primary tracking signal for PR deltas and historical charting.

## Local Run

```bash
pnpm nx run @benchmarks/bundle-size:build
```

Run one or more scenarios during local optimization:

```bash
pnpm nx run @benchmarks/bundle-size:build -- --scenario react-router.minimal
pnpm nx run @benchmarks/bundle-size:build -- --scenario react-router.minimal,react-router.full
```

Filtered runs build only the package projects needed by selected scenarios. Full runs build all package projects needed by all scenarios. If the required packages are already built and unchanged, skip that step:

```bash
pnpm nx run @benchmarks/bundle-size:build -- --scenario react-router.minimal --skip-package-builds
```

This writes:

- `benchmarks/bundle-size/results/current.json`
- `benchmarks/bundle-size/results/benchmark-action.json`

`current.json` includes run status, selected package build projects, per-scenario totals, per-file sizes, and the emitted JS files used for measurement. Dist paths use `scenarioDir`/`outDir`, e.g. `react-router.minimal` maps to `benchmarks/bundle-size/dist/react-router-minimal/`.

## Local Query Tools

```bash
pnpm benchmark:bundle-size:query --id react-router.minimal
pnpm benchmark:bundle-size:diff --baseline /tmp/base-current.json --id react-router.minimal
pnpm benchmark:bundle-size:history --id react-router.minimal --top-deltas 20
```

For source attribution, run an analysis build. This uses hidden source maps and writes source estimates into `current.json`; those estimates are for investigation only, not tracking.

```bash
pnpm nx run @benchmarks/bundle-size:build -- --scenario react-router.minimal --analysis
pnpm benchmark:bundle-size:analyze --id react-router.minimal --top-sources 30
```

## CI Reporting

- PR workflow generates a sticky comment with:
  - current gzip values
  - baseline delta
  - inline sparkline trend
- Pushes to `main` publish historical chart data to GitHub Pages via `benchmark-action/github-action-benchmark`.

## Manual Update Policy

When router/start public hooks/components evolve, update the corresponding `*-full/src/routes/__root.tsx` harness to keep full scenarios representative.

## Backfill Readiness

The measurement script supports optional interfaces for historical backfilling:

- `--sha`
- `--measured-at`
- `--append-history`
- `--scenario`
- `--analysis`
- `--sourcemap`
- `--skip-package-builds`

These are intended for one-off scripts that replay historical commits and append results to the same history dataset shape used for chart generation.
If `--append-history` points at a `data.js` file, output is written as `window.BENCHMARK_DATA = ...` for direct GitHub Pages compatibility.

---

## File: docs/start/eslint/eslint-plugin-start.md

---
id: eslint-plugin-start
title: ESLint Plugin Start
---

TanStack Start comes with its own ESLint plugin. This plugin is used to enforce best practices and help you avoid common mistakes when working with Server Components and server/client boundaries.

## Installation

The plugin is a separate package that you need to install:

```sh
npm install -D @tanstack/eslint-plugin-start
```

or

```sh
pnpm add -D @tanstack/eslint-plugin-start
```

or

```sh
yarn add -D @tanstack/eslint-plugin-start
```

or

```sh
bun add -D @tanstack/eslint-plugin-start
```

## Flat Config (`eslint.config.js`)

The release of ESLint 9.0 introduced a new way to configure ESLint using a flat config format. The TanStack Start ESLint Plugin supports this format and provides a recommended config you can use to enable all recommended rules.

### Recommended Flat Config setup

To enable all recommended rules:

```js
// eslint.config.js
import pluginStart from '@tanstack/eslint-plugin-start'

export default [
  pluginStart.configs['flat/recommended'],
  // Any other config...
]
```

### Custom Flat Config setup

Alternatively, load the plugin and configure only the rules you want:

```js
// eslint.config.js
import pluginStart from '@tanstack/eslint-plugin-start'

export default [
  {
    plugins: {
      '@tanstack/start': pluginStart,
    },
    rules: {
      '@tanstack/start/no-client-code-in-server-component': 'error',
      '@tanstack/start/no-async-client-component': 'error',
    },
  },
  // Any other config...
]
```

## Legacy Config (`.eslintrc`)

Prior to ESLint 9.0, the most common way to configure ESLint was using a `.eslintrc` file. The TanStack Start ESLint plugin still supports this configuration method.

### Recommended Legacy Config setup

To enable all recommended rules, add `plugin:@tanstack/eslint-plugin-start/recommended` in `extends`:

```json
{
  "extends": ["plugin:@tanstack/eslint-plugin-start/recommended"]
}
```

### Custom Legacy Config setup

Alternatively, add `@tanstack/eslint-plugin-start` to `plugins` and configure the rules you want:

```json
{
  "plugins": ["@tanstack/eslint-plugin-start"],
  "rules": {
    "@tanstack/start/no-client-code-in-server-component": "error",
    "@tanstack/start/no-async-client-component": "error"
  }
}
```

## Rules

The following rules are available in the TanStack Start ESLint plugin:

- [@tanstack/start/no-client-code-in-server-component](./no-client-code-in-server-component.md)
- [@tanstack/start/no-async-client-component](./no-async-client-component.md)

---

## File: docs/start/eslint/no-async-client-component.md

---
id: no-async-client-component
title: Disallow async components in client context
---

Async React components are only valid within React Server Components.

In TanStack Start, that means async components must only be used in server-component context, for example:

- rendered by `renderServerComponent(<AsyncComp />)`
- rendered inside a `createCompositeComponent((props) => <AsyncComp />)` callback

This rule helps you catch async components that have become "client" (directly or transitively), which would otherwise crash at runtime.

## Rule Details

Examples of **incorrect** code for this rule:

```tsx
/* eslint "@tanstack/start/no-async-client-component": "error" */

import { createFileRoute } from '@tanstack/react-router'

export async function Page() {
  return 
}

export const Route = createFileRoute(undefined)({
  component: Page,
})
```

Examples of **correct** code for this rule:

```tsx
/* eslint "@tanstack/start/no-async-client-component": "error" */

import { createFileRoute } from '@tanstack/react-router'

export function Page() {
  return 
}

export const Route = createFileRoute(undefined)({
  component: Page,
})
```

```tsx
/* eslint "@tanstack/start/no-async-client-component": "error" */

import { createCompositeComponent } from '@tanstack/react-start/rsc'

export const ServerPage = createCompositeComponent(async () => {
  const message = await Promise.resolve('hello')
  return {message}
})
```

## Reporting location

This rule can report in two ways:

- **Usage-site**: if a component becomes client due to being referenced by route options, the error should be reported at the route option usage.
- **Definition-site**: if a component is client due to `'use client'` or being rendered by a client component, the error is reported at the component definition.

## Attributes

- [x] ✅ Recommended

---

## File: docs/start/eslint/no-client-code-in-server-component.md

---
id: no-client-code-in-server-component
title: Disallow client-only code in server components
---

TanStack Start defines React Server Component boundaries via RSC helpers like `renderServerComponent()` and `createCompositeComponent()`.

- `renderServerComponent(<Element />)` is a server boundary around the element.
- `createCompositeComponent((props) => <Element />)` is a server boundary around the callback.

Inside these boundaries (and anything rendered from them, unless it crosses a `'use client'` boundary) you must not use client-only code.

This rule detects common client-only patterns in server components:

- React client hooks (eg `useState`, `useEffect`)
- browser-only globals (eg `window`, `document`)
- React event handlers (`onClick`, etc.)
- function props (non-serializable across the server/client boundary)
- class components

## Rule Details

Examples of **incorrect** code for this rule:

```tsx
/* eslint "@tanstack/start/no-client-code-in-server-component": "error" */

import { createCompositeComponent } from '@tanstack/react-start/rsc'
import { useState } from 'react'

export const Server = createCompositeComponent(() => {
  const [count] = useState(0)
  return {count}
})
```

```tsx
/* eslint "@tanstack/start/no-client-code-in-server-component": "error" */

import { createCompositeComponent } from '@tanstack/react-start/rsc'

export const Server = createCompositeComponent(() => {
  return {window.location.href}
})
```

Examples of **correct** code for this rule:

```tsx
/* eslint "@tanstack/start/no-client-code-in-server-component": "error" */

import { renderServerComponent } from '@tanstack/react-start/rsc'

export const Server = renderServerComponent(
  (async () => {
    const msg = await Promise.resolve('ok')
    return {msg}
  })(),
)
```

## Attributes

- [x] ✅ Recommended

---

## File: docs/start/framework/solid/guide/authentication.md

---
id: authentication
title: Authentication
---

This guide covers authentication patterns and shows how to implement your own authentication system with TanStack Start.

> **📋 Before You Start:** Check our [Authentication Overview](./authentication-overview.md) for all available options including partner solutions and hosted services.

## Authentication Approaches

You have several options for authentication in your TanStack Start application:

**Hosted Solutions:**

1. **[Clerk](https://clerk.dev)** - Complete authentication platform with UI components
2. **[WorkOS](https://workos.com)** - Enterprise-focused with SSO and compliance features
3. **[Better Auth](https://www.better-auth.com/)** - Open-source TypeScript library
4. **[Auth.js](https://authjs.dev/)** - Open-source library supporting 80+ OAuth providers

**DIY Implementation Benefits:**

- **Full Control**: Complete customization over authentication flow
- **No Vendor Lock-in**: Own your authentication logic and user data
- **Custom Requirements**: Implement specific business logic or compliance needs
- **Cost Control**: No per-user pricing or usage limits

Authentication involves many considerations including password security, session management, rate limiting, CSRF protection, and various attack vectors.

## Core Concepts

### Authentication vs Authorization

- **Authentication**: Who is this user? (Login/logout)
- **Authorization**: What can this user do? (Permissions/roles)

TanStack Start provides the tools for both through server functions, sessions, and route protection.

> Protect the data/API boundary first. Any server function, server route, or other API endpoint that returns or mutates private data must authorize the request itself. `beforeLoad` is useful for route UX: it keeps users out of screens they cannot use and avoids triggering work that would fail anyway. It is not the security boundary for the data. See [Authentication Server Primitives](./authentication-server-primitives.md) for the server-side pattern.

## Essential Building Blocks

### 1. Server Functions for Authentication

Server functions handle sensitive authentication logic securely on the server:

```tsx
import { createServerFn } from '@tanstack/solid-start'
import { redirect } from '@tanstack/solid-router'

// Login server function
export const loginFn = createServerFn({ method: 'POST' })
  .validator((data: { email: string; password: string }) => data)
  .handler(async ({ data }) => {
    // Verify credentials (replace with your auth logic)
    const user = await authenticateUser(data.email, data.password)

    if (!user) {
      return { error: 'Invalid credentials' }
    }

    // Create session
    const session = await useAppSession()
    await session.update({
      userId: user.id,
      email: user.email,
    })

    // Redirect to protected area
    throw redirect({ to: '/dashboard' })
  })

// Logout server function
export const logoutFn = createServerFn({ method: 'POST' }).handler(async () => {
  const session = await useAppSession()
  await session.clear()
  throw redirect({ to: '/' })
})

// Get current user
export const getCurrentUserFn = createServerFn({ method: 'GET' }).handler(
  async () => {
    const session = await useAppSession()
    const userId = session.get('userId')

    if (!userId) {
      return null
    }

    return await getUserById(userId)
  },
)
```

### 2. Session Management

TanStack Start provides secure HTTP-only cookie sessions:

```tsx
// utils/session.ts
import { useSession } from '@tanstack/solid-start/server'

type SessionData = {
  userId?: string
  email?: string
  role?: string
}

export function useAppSession() {
  return useSession<SessionData>({
    // Session configuration
    name: 'app-session',
    password: process.env.SESSION_SECRET!, // At least 32 characters
    // Optional: customize cookie settings
    cookie: {
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      httpOnly: true,
    },
  })
}
```

### 3. Authentication Context

Share authentication state across your application:

```tsx
// contexts/auth.tsx
import { createContext, useContext } from 'solid-js'
import { useServerFn } from '@tanstack/solid-start'
import { getCurrentUserFn } from '../server/auth'

type User = {
  id: string
  email: string
  role: string
}

type AuthContextType = {
  user: User | null
  isLoading: boolean
  refetch: () => void
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function AuthProvider(props) {
  const { data: user, isLoading, refetch } = useServerFn(getCurrentUserFn)

  return (
    <AuthContext.Provider value={{ user, isLoading, refetch }}>
      {props.children}
    </AuthContext.Provider>
  )
}

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider')
  }
  return context
}
```

### 4. Route Protection

Protect routes using `beforeLoad`:

```tsx
// routes/_authed.tsx - Layout route for protected pages
import { createFileRoute, redirect } from '@tanstack/solid-router'
import { getCurrentUserFn } from '../server/auth'

export const Route = createFileRoute('/_authed')({
  beforeLoad: async ({ location }) => {
    const user = await getCurrentUserFn()

    if (!user) {
      throw redirect({
        to: '/login',
        search: { redirect: location.href },
      })
    }

    // Pass user to child routes
    return { user }
  },
})
```

```tsx
// routes/_authed/dashboard.tsx - Protected route
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/_authed/dashboard')({
  component: DashboardComponent,
})

function DashboardComponent() {
  const context = Route.useRouteContext()

  return (
    
      <h1>Welcome, {context().user.email}!</h1>
      {/* Dashboard content */}
    
  )
}
```

## Implementation Patterns

### Basic Email/Password Authentication

```tsx
// server/auth.ts
import bcrypt from 'bcryptjs'
import { createServerFn } from '@tanstack/solid-start'

// User registration
export const registerFn = createServerFn({ method: 'POST' })
  .validator((data: { email: string; password: string; name: string }) => data)
  .handler(async ({ data }) => {
    // Check if user exists
    const existingUser = await getUserByEmail(data.email)
    if (existingUser) {
      return { error: 'User already exists' }
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(data.password, 12)

    // Create user
    const user = await createUser({
      email: data.email,
      password: hashedPassword,
      name: data.name,
    })

    // Create session
    const session = await useAppSession()
    await session.update({ userId: user.id })

    return { success: true, user: { id: user.id, email: user.email } }
  })

async function authenticateUser(email: string, password: string) {
  const user = await getUserByEmail(email)
  if (!user) return null

  const isValid = await bcrypt.compare(password, user.password)
  return isValid ? user : null
}
```

### Role-Based Access Control (RBAC)

```tsx
// utils/auth.ts
export const roles = {
  USER: 'user',
  ADMIN: 'admin',
  MODERATOR: 'moderator',
} as const

type Role = (typeof roles)[keyof typeof roles]

export function hasPermission(userRole: Role, requiredRole: Role): boolean {
  const hierarchy = {
    [roles.USER]: 0,
    [roles.MODERATOR]: 1,
    [roles.ADMIN]: 2,
  }

  return hierarchy[userRole] >= hierarchy[requiredRole]
}

// Protected route with role check
export const Route = createFileRoute('/_authed/admin/')({
  beforeLoad: async ({ context }) => {
    if (!hasPermission(context.user.role, roles.ADMIN)) {
      throw redirect({ to: '/unauthorized' })
    }
  },
})
```

### Social Authentication Integration

```tsx
// Example with OAuth providers
export const authProviders = {
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID!,
    redirectUri: `${process.env.APP_URL}/auth/google/callback`,
  },
  github: {
    clientId: process.env.GITHUB_CLIENT_ID!,
    redirectUri: `${process.env.APP_URL}/auth/github/callback`,
  },
}

export const initiateOAuthFn = createServerFn({ method: 'POST' })
  .validator((data: { provider: 'google' | 'github' }) => data)
  .handler(async ({ data }) => {
    const provider = authProviders[data.provider]
    const state = generateRandomState()

    // Store state in session for CSRF protection
    const session = await useAppSession()
    await session.update({ oauthState: state })

    // Generate OAuth URL
    const authUrl = generateOAuthUrl(provider, state)

    throw redirect({ href: authUrl })
  })
```

### Password Reset Flow

```tsx
// Password reset request
export const requestPasswordResetFn = createServerFn({ method: 'POST' })
  .validator((data: { email: string }) => data)
  .handler(async ({ data }) => {
    const user = await getUserByEmail(data.email)
    if (!user) {
      // Don't reveal if email exists
      return { success: true }
    }

    const token = generateSecureToken()
    const expires = new Date(Date.now() + 60 * 60 * 1000) // 1 hour

    await savePasswordResetToken(user.id, token, expires)
    await sendPasswordResetEmail(user.email, token)

    return { success: true }
  })

// Password reset confirmation
export const resetPasswordFn = createServerFn({ method: 'POST' })
  .validator((data: { token: string; newPassword: string }) => data)
  .handler(async ({ data }) => {
    const resetToken = await getPasswordResetToken(data.token)

    if (!resetToken || resetToken.expires < new Date()) {
      return { error: 'Invalid or expired token' }
    }

    const hashedPassword = await bcrypt.hash(data.newPassword, 12)
    await updateUserPassword(resetToken.userId, hashedPassword)
    await deletePasswordResetToken(data.token)

    return { success: true }
  })
```

## Security Best Practices

### 1. Password Security

```tsx
// Use strong hashing (bcrypt, scrypt, or argon2)
import bcrypt from 'bcryptjs'

const saltRounds = 12 // Adjust based on your security needs
const hashedPassword = await bcrypt.hash(password, saltRounds)
```

### 2. Session Security

```tsx
// Use secure session configuration
export function useAppSession() {
  return useSession({
    name: 'app-session',
    password: process.env.SESSION_SECRET!, // 32+ characters
    cookie: {
      secure: process.env.NODE_ENV === 'production', // HTTPS only in production
      sameSite: 'lax', // CSRF protection
      httpOnly: true, // XSS protection
      maxAge: 7 * 24 * 60 * 60, // 7 days
    },
  })
}
```

### 3. Rate Limiting

```tsx
// Simple in-memory rate limiting (use Redis in production)
const loginAttempts = new Map<string, { count: number; resetTime: number }>()

export const rateLimitLogin = (ip: string): boolean => {
  const now = Date.now()
  const attempts = loginAttempts.get(ip)

  if (!attempts || now > attempts.resetTime) {
    loginAttempts.set(ip, { count: 1, resetTime: now + 15 * 60 * 1000 }) // 15 min
    return true
  }

  if (attempts.count >= 5) {
    return false // Too many attempts
  }

  attempts.count++
  return true
}
```

### 4. Input Validation

```tsx
import { z } from 'zod'

const loginSchema = z.object({
  email: z.string().email().max(255),
  password: z.string().min(8).max(100),
})

export const loginFn = createServerFn({ method: 'POST' })
  .validator((data) => loginSchema.parse(data))
  .handler(async ({ data }) => {
    // data is now validated
  })
```

## Testing Authentication

### Unit Testing Server Functions

```tsx
// __tests__/auth.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { loginFn } from '../server/auth'

describe('Authentication', () => {
  beforeEach(async () => {
    await setupTestDatabase()
  })

  it('should login with valid credentials', async () => {
    const result = await loginFn({
      data: { email: 'test@example.com', password: 'password123' },
    })

    expect(result.error).toBeUndefined()
    expect(result.user).toBeDefined()
  })

  it('should reject invalid credentials', async () => {
    const result = await loginFn({
      data: { email: 'test@example.com', password: 'wrongpassword' },
    })

    expect(result.error).toBe('Invalid credentials')
  })
})
```

### Integration Testing

```tsx
// __tests__/auth-flow.test.tsx
import { render, screen, fireEvent, waitFor } from '@solidjs/testing-library'
import { RouterProvider, createMemoryHistory } from '@tanstack/solid-router'
import { router } from '../router'

describe('Authentication Flow', () => {
  it('should redirect to login when accessing protected route', async () => {
    const history = createMemoryHistory()
    history.push('/dashboard') // Protected route

    render(<RouterProvider router={router} history={history} />)

    await waitFor(() => {
      expect(screen.getByText('Login')).toBeInTheDocument()
    })
  })
})
```

## Common Patterns

### Loading States

```tsx
function LoginForm() {
  const [isLoading, setIsLoading] = createSignal(false)
  const loginMutation = useServerFn(loginFn)

  const handleSubmit = async (data: LoginData) => {
    setIsLoading(true)
    try {
      await loginMutation.mutate(data)
    } catch (error) {
      // Handle error
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      
        {isLoading() ? 'Logging in...' : 'Login'}
      
    </form>
  )
}
```

### Remember Me Functionality

```tsx
export const loginFn = createServerFn({ method: 'POST' })
  .validator(
    (data: { email: string; password: string; rememberMe?: boolean }) => data,
  )
  .handler(async ({ data }) => {
    const user = await authenticateUser(data.email, data.password)
    if (!user) return { error: 'Invalid credentials' }

    const session = await useAppSession()
    await session.update(
      { userId: user.id },
      {
        // Extend session if remember me is checked
        maxAge: data.rememberMe ? 30 * 24 * 60 * 60 : undefined, // 30 days vs session
      },
    )

    return { success: true }
  })
```

## Migration from Other Solutions

### From Client-Side Auth

If you're migrating from client-side authentication (localStorage, context only):

1. Move authentication logic to server functions
2. Replace localStorage with server sessions
3. Update route protection to use `beforeLoad`
4. Add proper security headers and CSRF protection

### From Other Frameworks

- **Next.js**: Replace API routes with server functions, migrate NextAuth sessions
- **Remix**: Convert loaders/actions to server functions, adapt session patterns
- **SvelteKit**: Move form actions to server functions, update route protection

## Production Considerations

When choosing your authentication approach, consider these factors:

### Hosted vs DIY Comparison

**Hosted Solutions (Clerk, WorkOS, Better Auth):**

- Pre-built security measures and regular updates
- UI components and user management features
- Compliance certifications and audit trails
- Support and documentation
- Per-user or subscription pricing

**DIY Implementation:**

- Complete control over implementation and data
- No ongoing subscription costs
- Custom business logic and workflows
- Responsibility for security updates and monitoring
- Need to handle edge cases and attack vectors

### Security Considerations

Authentication systems need to handle various security aspects:

- Password hashing and timing attack prevention
- Session management and fixation protection
- CSRF and XSS protection
- Rate limiting and brute force prevention
- OAuth flow security
- Compliance requirements (GDPR, CCPA, etc.)

## Next Steps

When implementing authentication, consider:

- **Security Review**: Review your implementation for security best practices
- **Performance**: Add caching for user lookups and session validation
- **Monitoring**: Add logging and monitoring for authentication events
- **Compliance**: Ensure compliance with relevant regulations if storing personal data

For other authentication approaches, check the [Authentication Overview](./authentication-overview.md). For specific integration help, see the [How-to Guides](/router/latest/docs/framework/solid/how-to/README.md#authentication) or explore our [working examples](https://github.com/TanStack/router/tree/main/examples/solid).

---

## File: docs/start/framework/solid/guide/authentication-overview.md

---
ref: docs/start/framework/react/guide/authentication-overview.md
replace:
  {
    '@tanstack/react-start': '@tanstack/solid-start',
    'React': 'SolidJS',
    'react-router': 'solid-router',
  }
---

---

## File: docs/start/framework/solid/guide/client-entry-point.md

---
id: client-entry-point
title: Client Entry Point
---

# Client Entry Point

> [!NOTE]
> The client entry point is **optional** out of the box. If not provided, TanStack Start will automatically handle the client entry point for you using the below as a default.

Getting our html to the client is only half the battle. Once there, we need to hydrate our client-side JavaScript once the route resolves to the client. We do this by hydrating the root of our application with the `StartClient` component:

```tsx
// src/client.tsx
import { hydrate } from 'solid-js/web'
import { StartClient, hydrateStart } from '@tanstack/solid-start/client'

hydrateStart().then((router) => {
  hydrate(() => <StartClient router={router} />, document)
})
```

This enables us to kick off client-side routing once the user's initial server request has fulfilled.

## Error Handling

You can wrap your client entry point with error boundaries to handle client-side errors gracefully:

```tsx
// src/client.tsx
import { StartClient } from '@tanstack/solid-start/client'
import { hydrate } from 'solid-js/web'
import { ErrorBoundary } from './components/ErrorBoundary'

hydrate(
  () => (
    <ErrorBoundary>
      <StartClient />
    </ErrorBoundary>
  ),
  document.body,
)
```

## Development vs Production

You may want different behavior in development vs production:

```tsx
// src/client.tsx
import { StartClient } from '@tanstack/solid-start/client'
import { hydrate } from 'solid-js/web'

const App = (
  <>
    {import.meta.env.DEV && Development Mode}
    <StartClient />
  </>
)

hydrate(() => <App />, document.body)
```

The client entry point gives you full control over how your application initializes on the client side while working seamlessly with TanStack Start's server-side rendering.