# Add Clerk authentication to a TanStack Start app with the Clerk CLI

**How do I add Clerk authentication to a TanStack Start app with the Clerk CLI?**

Run `clerk init --framework tanstack-start` against an existing TanStack Start app — the [Clerk CLI](https://clerk.com/changelog/2026-04-22-clerk-cli.md) (released 2026-04-22) wires `<ClerkProvider>` into `__root.tsx`, generates catch-all `sign-in` / `sign-up` routes, drops a server `start.ts` with `clerkMiddleware()` as request middleware, and adds `@clerk/tanstack-react-start` to your dependencies. Follow that with `clerk env pull`, `clerk doctor`, and `clerk config patch` to manage [authentication](https://clerk.com/glossary.md#authentication) configuration — [passkeys](https://clerk.com/glossary.md#passkeys), sign-in methods, [session](https://clerk.com/glossary.md#session) policy — as code. The walkthrough below covers the full flow end-to-end against a fresh `@tanstack/cli` scaffold, including a sign-in affordance on the landing page and the Core 3 `<Show when="signed-in">` component that replaces `<SignedIn>`.

Validated against TanStack Start **1.167.42**, `@clerk/tanstack-react-start` **1.1.5**, Clerk CLI **1.0.2**, React **19.2.5**, and Vite **8.0.10** on 2026-04-23 — pin equivalent versions if anything drifts. TanStack Start has no `middleware.ts`/`proxy.ts` convention like Next.js and many existing tutorials predate the CLI or mix Core 2 component names that [Core 3 removed](https://clerk.com/changelog/2026-03-03-core-3.md); the CLI output tracks the current SDK surface area instead.

## Why the Clerk CLI for TanStack Start

TanStack Start is SSR-first React with server functions and `beforeLoad` route guards — there's no `middleware.ts` / `proxy.ts` convention like Next.js, and the integration points for auth are less obvious the first time you look at a Start project. Most existing tutorials either pre-date the CLI, pre-date `@clerk/tanstack-react-start`'s current shape (older posts reference the renamed `@clerk/tanstack-start` package), or mix Core 2 component names like `<SignedIn>` / `<SignedOut>` that [Core 3 removed](https://clerk.com/changelog/2026-03-03-core-3.md).

`clerk init` skips all of that. It detects the framework from `package.json`, installs the right SDK, writes the provider + middleware + auth-page scaffolding, and seeds `.env.local` in one shot. Because the CLI is version-current, the output tracks `@clerk/tanstack-react-start`'s current surface area, not whatever shipped eight months ago. If you're coming from the Next.js version of this workflow, the spine is the same — the CLI runs the same way across frameworks — but the files it writes are Start-shaped.

> This article shows `clerk init` running on a project you already scaffolded yourself — the "add auth to my existing app" path. `clerk init --starter --framework tanstack-start` exists for the opposite case (bootstrap a new template). `--starter` is a boolean flag that pairs with `--framework`, not a value-bearing option. The full flag list is in `clerk init --help`.

## Prerequisites

- [ ] **Node.js 20+** — TanStack Start's current minimum.
- [ ] **pnpm** — this article uses pnpm throughout. npm, yarn, and bun work too. `clerk init` detects the package manager from your lockfile (`pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, or the absence of any lockfile → npm), so switching just means running the matching scaffold command.
- [ ] **A Clerk account** — free tier is fine for everything in the first half of the article. The config-as-code section toggles [passkey](https://clerk.com/glossary.md#passkeys) sign-in and a [session](https://clerk.com/glossary.md#session) config, both of which [require a paid plan](https://clerk.com/pricing). The article notes where to stop if you're staying on free.
- [ ] **The `clerk` CLI binary on your `$PATH`** — installed in the next section.

TanStack Start is currently in Release Candidate: the [official overview](https://tanstack.com/start/latest/docs/framework/react/overview) describes the API as feature-complete but not guaranteed bug-free. Pin a known-good version for production workloads.

## Install or update the Clerk CLI

New install — pick whichever fits your machine. The shell one-liner is the fastest path, but Homebrew and a global npm install both work:

**curl**

filename: terminal
```sh
curl -fsSL https://clerk.com/install | sh
```

**Homebrew**

filename: terminal
```sh
brew install clerk/stable/clerk
```

**npm**

filename: terminal
```sh
npm install -g clerk
```

Confirm the version — you want `1.0.2` or later for this tutorial (the 2026-04-22 release):

```bash
clerk --version
```

The CLI has no self-update subcommand. To upgrade, rerun whichever installer you used. The curl installer fetches the latest release by default; pass `--canary` to track the edge channel. For Homebrew, use the standard upgrade subcommand. For a global npm install, reinstall the `@latest` tag to pull the newest release:

**curl**

filename: terminal
```sh
curl -fsSL https://clerk.com/install | sh -s -- --canary
```

**Homebrew**

filename: terminal
```sh
brew upgrade clerk
```

**npm**

filename: terminal
```sh
npm install -g clerk@latest
```

Optional but recommended — enable shell completion so subcommand and flag names autocomplete:

**zsh**

filename: terminal
```sh
clerk completion zsh > "${fpath[1]}/_clerk"
```

**bash**

filename: terminal
```sh
clerk completion bash > /etc/bash_completion.d/clerk
```

## Install the Clerk agent skills

If you're using Claude Code, Cursor, or Codex, you have two paths to get the Clerk-maintained skills into your project:

- **Let `clerk init` do it** (recommended). The next step — `clerk init` — ends with an `Install agent skills?` prompt that defaults to yes. Accept it, or pass `-y` to skip the prompt and auto-accept. The CLI then runs `npx skills add clerk/skills` under the hood.
- **Install manually** (if you want skills in a project you're not scaffolding with `clerk init`, or if you skipped the prompt):

```bash
npx skills add clerk/skills
```

For TanStack Start, `clerk init` installs three skills: `clerk` (the CLI + core concepts), `clerk-setup` (the quickstart surface), and `clerk-tanstack-patterns` (TanStack-specific auth patterns — loaders, `beforeLoad`, server functions). The base two ship with every framework; the third is selected by matching `@tanstack/react-start` in `package.json` against the CLI's framework → skill map.

Coding agents trained on older Clerk content tend to hallucinate the removed `<SignedIn>` / `<SignedOut>` components, the old `@clerk/tanstack-start` package name, and long-retired patterns. The bundled skills are how you keep them honest.

> Clerk ships breaking changes on a regular cadence. Refresh skills by rerunning `npx skills add clerk/skills` — the command is idempotent and picks up new versions each time. Rerunning `clerk init` also reinstalls them as part of the flow.

If you already have hand-rolled `clerk-*` skills under `.claude/skills/` from an earlier project, audit them after the install: the Clerk-maintained skills supersede most hand-authored ones and the overlap will confuse your agent. To suppress the prompt inside `clerk init` (so it doesn't try to reinstall), pass `--no-skills`.

## Log in and pick an application

```bash
clerk auth login
clerk whoami
```

`clerk auth login` opens the dashboard in a browser, completes [OAuth](https://clerk.com/glossary.md#oauth), and persists credentials and config to the OS-standard CLI directory (macOS: `~/Library/Preferences/clerk-cli/config.json`, Linux: `~/.config/clerk-cli/config.json`, Windows: `%APPDATA%\clerk-cli\Config\config.json`). Override with `CLERK_CONFIG_DIR` if you need a custom location, or run `clerk doctor` to print the resolved path. `clerk whoami` confirms which account you're operating as — useful when you have multiple logins or switch between personal and work accounts.

List your apps and pick one:

```bash
clerk apps list
clerk apps list --json  # machine-readable output, pipe to jq for scripts
```

If you don't have an app yet, create one from the CLI:

```bash
clerk apps create "my-tanstack-app"
```

Or let `clerk init` prompt you interactively later. Either path works — the article's validation pre-linked a test app with `clerk link --app <id>` so `clerk init` could skip the app-picker prompt.

## Scaffold a TanStack Start app with `@tanstack/cli`

Create a working directory and scaffold Start:

```bash
pnpm dlx @tanstack/cli@latest create my-clerk-tanstack-start-app
```

The TanStack scaffolder is interactive. Accept Tailwind CSS, decline ESLint (add it back later if you want — it's out of scope here), and take defaults for the rest. The version-pinned non-interactive equivalent:

```bash
pnpm dlx @tanstack/cli@latest create my-clerk-tanstack-start-app \
  --framework React \
  --package-manager pnpm \
  --no-toolchain \
  --no-examples \
  --no-git \
  --yes
```

> TanStack CLI prompts drift between versions. If the interactive prompts look different from the list above, accept Tailwind and decline ESLint — other toggles don't affect this tutorial. The non-interactive form is more stable if you're automating.

Move into the new app and confirm it boots:

```bash
cd my-clerk-tanstack-start-app
pnpm install
pnpm dev
```

You should see a "Welcome to TanStack Start" page on `http://localhost:3000`. TanStack Start pins `vite dev --port 3000` in the scaffolded `package.json`. Stop the dev server (Ctrl-C) before the next step — `clerk init` writes files you'd rather not have HMR-reloaded mid-flight.

The scaffolded structure you care about:

```
src/
├── router.tsx
├── routes/
│   ├── __root.tsx
│   └── index.tsx
├── routeTree.gen.ts
├── start.ts
└── styles.css
```

Note the modern `src/`-rooted layout. Older TanStack Start content still references an `app/` layout — that's been replaced. `clerk init` targets `src/` correctly; if you're migrating an older app, move the files first. The scaffold's `src/start.ts` is where `clerk init` inserts `clerkMiddleware()` (see appendix).

## Add Clerk with `clerk init`

From inside the `my-clerk-tanstack-start-app/` directory:

```bash
clerk init --framework tanstack-start
```

The CLI auto-detects the framework from `package.json`, so `--framework tanstack-start` is technically redundant — but explicit is worth the typing for reproducibility and CI scripts. The package manager is auto-detected from the lockfile the same way. Pass `-y` for non-interactive mode in CI (accepts the scaffold plan, skips the skills prompt by auto-accepting), or leave it off locally to preview the plan before it writes:

```bash
clerk init --framework tanstack-start -y
```

If you want `clerk init` to pull keys for a specific app without interactive picking, link the app first:

```bash
clerk link --app app_xxx
clerk init --framework tanstack-start -y
```

`clerk link --app <id>` writes the link to the CLI config directory. When `clerk init` runs afterward, `link({ skipIfLinked: true })` finds the existing link and skips the prompt, and `env pull` picks up the linked app's keys. Without a pre-existing link, `clerk init` runs `clerk link` interactively so you can pick or create an app during the flow. (`clerk init` itself has no `--app` flag — only `link`, `env pull`, `config`, and `api` do.)

What changes in your project when `clerk init` runs:

- **`package.json`** — adds `"@clerk/tanstack-react-start": "^1.1.5"` to `dependencies`. Note the `-react-` infix; the older `@clerk/tanstack-start` package name was renamed.
- **`src/routes/__root.tsx`** — wraps `{children}` in [`<ClerkProvider>`](https://clerk.com/glossary.md#clerkprovider) from `@clerk/tanstack-react-start`.
- **`src/routes/sign-in.$.tsx`** (new) — catch-all route rendering `<SignIn />`.
- **`src/routes/sign-up.$.tsx`** (new) — catch-all route rendering `<SignUp />`.
- **`src/start.ts`** — adds `clerkMiddleware()` to the `requestMiddleware` array returned by `createStart()`. TanStack's scaffold creates `src/start.ts` with an empty middleware config; `clerk init` modifies the existing file rather than writing a new one.
- **`.env.local`** — seeded with Clerk env vars. The route URL vars (`VITE_CLERK_SIGN_IN_URL`, `VITE_CLERK_SIGN_UP_URL`, and the two `_FALLBACK_REDIRECT_URL` vars) are written by the framework scaffold step. The keys (`VITE_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`) are written by `clerk init`'s built-in `env pull`, which runs after authentication and `link` succeed — so real keys land in the file in one pass as long as you're logged in and have either pre-linked an app or picked one at the prompt.

The full file-by-file diff is in the [appendix below](#appendix-what-clerk-init-wrote-for-you).

> With `-y`, `clerk init` rewrites `src/routes/__root.tsx` without prompting — but it **wraps** your existing JSX in `<ClerkProvider>`, it doesn't replace it. The only observed side-effect is cosmetic: the existing `<TanStackDevtools>` block loses two spaces of indentation and a trailing newline. Run `pnpm format` (or `prettier --write`) and the diff disappears. Commit before running the command if you want a clean before/after.

Install the new dependency:

```bash
pnpm install
```

`clerk init` does **not** run the install for you. That's intentional — it means you control when your lockfile updates — but easy to forget.

> Inside a pnpm monorepo, the scaffolded app will be slurped into the parent workspace unless it has its own sentinel. Add `pnpm-workspace.yaml` at the top of `my-clerk-tanstack-start-app/` (an empty file is fine) to isolate it before you run `pnpm install`. Standalone projects don't hit this.

## Link to an existing Clerk app (optional)

If you want to reassign the CLI to a different app after `clerk init`:

```bash
clerk unlink
clerk link --app app_xxx
```

`clerk whoami` reflects the currently-linked app and instance, so run it to sanity-check which environment you're pointing at before you make config changes. The CLI stores the active app and [secret key](https://clerk.com/glossary.md#secret-key) reference in the OS-standard CLI config directory (see the [previous section](#log-in-and-pick-an-application) for the per-platform path), not in your repo.

## Pull (or refresh) environment variables

```bash
clerk env pull
```

`clerk init` already runs `env pull` internally once authentication + `link` succeed, so right after a successful `clerk init` your `.env.local` is already populated. `clerk env pull` is the standalone command for everything afterward — refreshing keys after a rotation, switching between dev and prod (`--instance prod`), targeting a different app (`--app <id>`), or repopulating the file after you accidentally deleted it.

TanStack Start uses Vite, which reads `VITE_`-prefixed env vars on the client — so your publishable key lands as `VITE_CLERK_PUBLISHABLE_KEY`, not Next's `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`. The CLI handles the prefix difference automatically based on the framework it detects.

After `clerk init` (or after a manual `clerk env pull`), `.env.local` looks like this (real values redacted):

```bash
VITE_CLERK_SIGN_IN_URL=/sign-in
VITE_CLERK_SIGN_UP_URL=/sign-up
VITE_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
VITE_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/

# Clerk
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
```

Two categories of vars here. The four `VITE_CLERK_*_URL` entries are seeded by `clerk init`'s framework scaffold step (they wire the routes Clerk's components navigate to). The two keys are written by `clerk init`'s built-in `env pull` against the linked app and can be refreshed any time with a standalone `clerk env pull`.

> Targeting production? `clerk env pull --instance prod` pulls prod keys into `.env.production.local`. Keep dev and prod files separate.

Your `CLERK_SECRET_KEY` is a [secret key](https://clerk.com/glossary.md#secret-key) — never commit it, never ship it to the client. The default `.gitignore` that `@tanstack/cli` writes includes `.env*.local`, which covers this, but double-check.

## Run `clerk doctor` the first time (it should fail)

Before pulling env vars, `clerk doctor` is a teaching moment:

```bash
mv .env.local .env.local.bak
clerk doctor
```

Expected output flags the missing keys. Doctor validates the CLI version, auth session, linked app + instance IDs, and `.env.local` contents — the things that actually break integrations. It's framework-agnostic plumbing validation, not a framework-aware linter. It doesn't lint your `start.ts` or route files for TanStack-specific wiring.

Restore the file:

```bash
mv .env.local.bak .env.local
```

## Run `clerk doctor` after env pull (green)

Now re-run doctor:

```bash
clerk doctor
```

Every check should pass. If anything red remains:

- **`clerk doctor --spotlight`** filters output to warnings and failures only — useful when most checks pass and you want to focus on what's broken without scrolling.
- **`clerk doctor --fix`** runs in interactive mode and prompts per issue before applying a fix, then re-runs checks to verify. Skip it in CI; it needs a TTY.
- **`clerk doctor --verbose`** shows the full per-check output. Helpful when a check fails for a non-obvious reason.

> `clerk doctor` is the fastest way to diagnose "my keys aren't loading" in CI. Wire it into a preflight step alongside `pnpm install` and you'll catch most env-misconfigurations before they become runtime failures.

## Wire up the landing page

`clerk init` deliberately leaves `src/routes/index.tsx` alone — it's your landing page, and the CLI doesn't make assumptions about your layout. That means out of the box the app has no sign-in affordance on the home page. Add one:

```tsx
import { Show, SignInButton, SignUpButton, UserButton } from '@clerk/tanstack-react-start'
import { createFileRoute } from '@tanstack/react-router'

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

function Home() {
  return (
    <div className="p-8">
      <header className="mb-8 flex items-center justify-end gap-3">
        <Show when="signed-out">
          <SignInButton mode="modal" />
          <SignUpButton mode="modal" />
        </Show>
        <Show when="signed-in">
          <UserButton />
        </Show>
      </header>
      <h1 className="text-4xl font-bold">Welcome to TanStack Start</h1>
      <p className="mt-4 text-lg">
        Edit `src/routes/index.tsx` to get started.
      </p>
    </div>
  )
}
```

> Clerk Core 3 (released [2026-03-03](https://clerk.com/changelog/2026-03-03-core-3.md)) removed `<SignedIn>`, `<SignedOut>`, and `<Protect>` and replaced them with a single `<Show>` component. Any older TanStack Start tutorial you find on the internet still showing `<SignedIn>` / `<SignedOut>` is pre-Core-3 — port it. The [Core 3 upgrade guide](https://clerk.com/docs/guides/development/upgrading/upgrade-guides/core-3.md) has a `npx @clerk/upgrade` codemod that handles most call sites automatically.

This is the minimum viable landing page. The `mode="modal"` prop opens the sign-in/sign-up components in a modal rather than routing to `/sign-in` or `/sign-up`. Drop `mode="modal"` if you prefer full-page navigation to the catch-all routes `clerk init` generated.

## Start the dev server and sign up a test user

```bash
pnpm dev
```

Open `http://localhost:3000`. You should see the "Welcome to TanStack Start" header with **Sign in** / **Sign up** buttons in the top right. Click **Sign up**, run through the flow, and you'll land back on the home page with a `<UserButton />` in place of the sign-in/sign-up pair.

If the buttons don't render, two things to check before anything else:

1. **Restart the dev server.** Vite caches aggressively and new env vars don't always hot-reload.
2. **`clerk doctor`.** Missing publishable key is the single most common cause, and doctor catches it in one shot.

## Configure Clerk as code: `clerk config`

`clerk config` treats your Clerk instance configuration as data. You can pull the current state, diff it, patch fields, and audit the result. It's the same surface whether you're on dev or prod — add `--instance prod` when you're ready to push changes upstream.

Start by inspecting the schema:

```bash
clerk config schema
clerk config schema --keys auth_passkey session auth_attack_protection
```

`clerk config schema` prints the entire config surface. `--keys` scopes it — useful when you know the key names and just want the shape.

Pull the current config as a baseline:

```bash
clerk config pull --output config.before.json
```

Four patches follow. Run each `--dry-run` first; the dry run prints the exact shape that would be sent without making changes. Drop `--dry-run` and add `--yes` to commit.

**Patch 1: block disposable email domains** (no paid-plan gate):

```bash
clerk config patch --dry-run --json '{"auth_access_control":{"block_disposable_email_domains":true}}'
clerk config patch --json '{"auth_access_control":{"block_disposable_email_domains":true}}' --yes
```

**Patch 2: tighten lockout to 10 failed attempts** (no paid-plan gate — [the default is 100](https://clerk.com/docs/guides/secure/user-lockout.md)):

```bash
clerk config patch --dry-run --json '{"auth_attack_protection":{"user_lockout":{"max_attempts":10}}}'
clerk config patch --json '{"auth_attack_protection":{"user_lockout":{"max_attempts":10}}}' --yes
```

**Patch 3: enable passkeys as a sign-in factor** (paid plan required):

```bash
clerk config patch --dry-run --json '{"auth_passkey":{"used_for_sign_in":true}}'
clerk config patch --json '{"auth_passkey":{"used_for_sign_in":true}}' --yes
```

> [Passkeys](https://clerk.com/glossary.md#passkeys) can be **registered** on any plan; using them as a **sign-in factor** is a paid-plan feature. The `used_for_sign_in: true` flip is what unlocks the sign-in affordance in the `<SignIn />` component. See [pricing](https://clerk.com/pricing).

**Patch 4: tune session config** (paid plan required):

```bash
clerk config patch --dry-run --json '{"session":{"allowed_clock_skew":5,"claims":{},"lifetime":3600}}'
clerk config patch --json '{"session":{"allowed_clock_skew":5,"claims":{},"lifetime":3600}}' --yes
```

`allowed_clock_skew` tolerates small time drift between client and server (seconds). `claims` is where you inject custom [session](https://clerk.com/glossary.md#session) claims. `lifetime` is the session token lifetime in seconds (3600 = 1 hour).

> Session config changes are gated on a paid plan. If you hit a 403 on this patch, that's the plan gate — not a CLI bug.

Pull the new config and diff it:

```bash
clerk config pull --output config.after.json
diff config.before.json config.after.json
```

You should see four blocks: `block_disposable_email_domains` flipped, `max_attempts` dropped from 100 to 10, `used_for_sign_in` flipped to `true`, and a full `session` object materialized (it was `null` before Patch 4).

> `clerk config put` replaces your entire config with the contents of a JSON file. It's destructive — use `patch` for day-to-day changes. `put` is for bootstrapping a new environment from a template, not for tuning.

## Verify the config changes worked

Reload the app and walk the sign-up flow again. A few things to confirm:

- **Disposable-email blocking.** Try signing up with a `@mailinator.com` address. The signup should be rejected at the email step.
- **Lockout.** Fail sign-in 10 times on purpose. The account should lock.
- **Passkey sign-in.** Sign in with your test user, open `<UserProfile />` (add a `/user` route rendering `<UserProfile />` if you don't have one yet), go to the **Security** tab, and register a passkey. Sign out, then sign in again — "Continue with passkey" should appear above the email field. Validated with platform authenticators (Touch ID, Windows Hello) and Bitwarden.
- **Session lifetime.** Signed-in sessions now expire after 1 hour instead of the default. Easy to verify in a long-running tab; easy to forget in dev unless you leave one open.

## Inspect your instance with `clerk api`

`clerk api` is a direct wrapper around the Clerk Backend API and (with `--platform`) the Clerk Platform API. It authenticates with your linked app's keys, so you don't need to craft curl commands or copy `CLERK_SECRET_KEY` into Postman.

List available endpoints and commands:

```bash
clerk api ls
clerk api ls users
```

List users:

```bash
clerk api /users
clerk api /users?limit=5
```

Fetch a single user:

```bash
clerk api /users/<user_id>
```

The Platform API (cross-instance application management) lives behind `--platform`. The CLI auto-prepends `/v1` and points at the Platform API host (`api.clerk.com`). Platform resources are namespaced under `/platform/`, so the full path for listing applications is `/platform/applications` — the CLI resolves this to `api.clerk.com/v1/platform/applications`:

```bash
clerk api --platform /platform/applications
```

Backend API calls (no `--platform`) hit `api.clerk.dev` instead, and their paths do not need the `/platform/` prefix — `/users` resolves to `api.clerk.dev/v1/users`, `/organizations` to `api.clerk.dev/v1/organizations`.

> If a `--platform` call returns `clerk_key_invalid`, the usual cause is a missing auth token rather than a bad path — `clerk api --platform` uses the OAuth token from `clerk auth login`, not `CLERK_SECRET_KEY`. Re-run `clerk auth login` if the token has expired. If you get `404`, double-check the path against the Platform API spec: Platform resources need the `/platform/` segment, Backend resources do not.

`clerk api` isn't a replacement for the full Backend SDK in application code — it's for one-off inspection, ops, and scripts.

## Appendix: what `clerk init` wrote for you

If you ever need to reproduce `clerk init`'s output by hand — porting to a non-supported framework, or just curious — here's the exact surface. Validated against TanStack Start 1.167.42 + `@clerk/tanstack-react-start@1.1.5`.

**`package.json`** — one dependency added:

```json
{
  "dependencies": {
    "@clerk/tanstack-react-start": "^1.1.5"
  }
}
```

**`src/routes/__root.tsx`** — existing content wrapped in `<ClerkProvider>` (reformatted by `pnpm format`):

```tsx
import { ClerkProvider } from '@clerk/tanstack-react-start'
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { TanStackDevtools } from '@tanstack/react-devtools'

import appCss from '../styles.css?url'

export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { title: 'TanStack Start Starter' },
    ],
    links: [{ rel: 'stylesheet', href: appCss }],
  }),
  shellComponent: RootDocument,
})

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <ClerkProvider>
          {children}
          <TanStackDevtools
            config={{ position: 'bottom-right' }}
            plugins={[
              {
                name: 'Tanstack Router',
                render: <TanStackRouterDevtoolsPanel />,
              },
            ]}
          />
          <Scripts />
        </ClerkProvider>
      </body>
    </html>
  )
}
```

**`src/routes/sign-in.$.tsx`** (new) — catch-all route so `<SignIn />` handles any sub-path (Clerk uses this for flow steps like `/sign-in/factor-two`):

```tsx
import { SignIn } from '@clerk/tanstack-react-start'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/sign-in/$')({
  component: Page,
})

function Page() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignIn />
    </div>
  )
}
```

**`src/routes/sign-up.$.tsx`** (new) — mirror of sign-in for sign-up:

```tsx
import { SignUp } from '@clerk/tanstack-react-start'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/sign-up/$')({
  component: Page,
})

function Page() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignUp />
    </div>
  )
}
```

**`src/start.ts`** (modified) — Clerk's request middleware slotted into TanStack Start's server instance (TanStack's scaffold creates this file; `clerk init` adds the `clerkMiddleware()` call and the `@clerk/tanstack-react-start/server` import):

```ts
import { clerkMiddleware } from '@clerk/tanstack-react-start/server'
import { createStart } from '@tanstack/react-start'

export const startInstance = createStart(() => {
  return {
    requestMiddleware: [clerkMiddleware()],
  }
})
```

This is the TanStack Start equivalent of Next's `proxy.ts` / middleware — the hook-point where Clerk resolves the session for every server-side request. `createStart` takes [a callback that returns the config](https://github.com/TanStack/router/blob/main/packages/start-client-core/src/createStart.ts), not a plain object. `clerkMiddleware()` populates `getAuth(req)` inside server functions and loaders so you can gate them with `beforeLoad` or by checking `userId` before returning data.

**`.env.local`** — the four route URL vars come from the framework scaffold step, the two key vars come from `clerk init`'s built-in `env pull` (real values land whenever you pre-link an app with `clerk link --app <id>` or pick an app at the interactive prompt):

```bash
VITE_CLERK_SIGN_IN_URL=/sign-in
VITE_CLERK_SIGN_UP_URL=/sign-up
VITE_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
VITE_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/

# Clerk
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
```

What `clerk init` does **not** touch: `src/routes/index.tsx` (landing page), `vite.config.ts`, `src/router.tsx`, `src/styles.css`, `README.md`. That's why the "wire up the landing page" step exists — you're filling in the piece the CLI intentionally left under your control.

## CLI reference (quick skim)

Commands the article touched:

| Command                                       | What it does                                                                                                                 |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `clerk auth login`                            | OAuth login, persists to the OS-standard CLI config directory (override with `CLERK_CONFIG_DIR`).                            |
| `clerk whoami`                                | Show current user + linked app.                                                                                              |
| `clerk apps list`                             | List apps in your org (add `--json` for machine-readable output).                                                            |
| `clerk apps create <name>`                    | Create a new app from the CLI.                                                                                               |
| `clerk init --framework tanstack-start`       | Install SDK + wire providers/routes/middleware. Auto-detects framework/package manager when the flag is omitted.             |
| `clerk link --app app_xxx`                    | Point the CLI at an existing app (run before `clerk init` to skip the app-picker).                                           |
| `clerk unlink`                                | Unlink the current app (add `--yes` to skip the confirmation prompt).                                                        |
| `clerk env pull`                              | Write env vars to `.env.local` (or `.env.production.local` with `--instance prod`; target a specific app with `--app <id>`). |
| `clerk doctor`                                | Validate auth + linked app + env vars.                                                                                       |
| `clerk config schema`                         | Print the full config schema.                                                                                                |
| `clerk config pull --output file.json`        | Snapshot current config.                                                                                                     |
| `clerk config patch --json '...' --dry-run`   | Preview a partial update.                                                                                                    |
| `clerk config patch --json '...' --yes`       | Commit the update.                                                                                                           |
| `clerk config put --file file.json`           | **Destructive** — replace config from file.                                                                                  |
| `clerk api /users`                            | Query the Clerk Backend API at `api.clerk.dev` (CLI auto-prepends `/v1`).                                                    |
| `clerk api --platform /platform/applications` | Query the Platform API at `api.clerk.com` (CLI auto-prepends `/v1`; Platform resources live under `/platform/`).             |
| `clerk completion zsh`                        | Print shell completion (pipe to your completion dir).                                                                        |
| `npx skills add clerk/skills`                 | Install/refresh the Clerk agent skills. Run inside `clerk init` or manually.                                                 |

Flags worth memorizing:

- `--yes` / `-y` — non-interactive; accept defaults.
- `--dry-run` — print the operation without executing (`config patch`, `config put`, `api`).
- `--instance prod` — target production instead of dev.
- `--app app_xxx` — scope the command to a specific app (valid on `link`, `env pull`, `config *`, `api` — **not** on `init`).
- `--no-skills` — skip the agent-skills prompt inside `clerk init`.

To upgrade the CLI itself, rerun the installer (`curl -fsSL https://clerk.com/install | sh`, `brew upgrade clerk`, or `npm install -g clerk@latest`). There is no `clerk update` subcommand in 1.0.2.

Full reference: [Clerk CLI docs](https://clerk.com/docs/cli.md).

## FAQ

## FAQ

### Is TanStack Start production-ready?

TanStack Start is in Release Candidate as of 2026-04. The official overview describes the API as feature-complete but not bug-free. Teams are shipping it to production; the advice is to pin a known-good version (this article was validated on TanStack Start 1.167.42) and read [TanStack's status page](https://tanstack.com/start/latest/docs/framework/react/overview) before committing.

### What does clerk init --framework tanstack-start actually change in my project?

On a current `@tanstack/cli` scaffold it adds `@clerk/tanstack-react-start` to `dependencies`, wraps the tree in `<ClerkProvider>` inside `src/routes/__root.tsx`, creates catch-all routes `src/routes/sign-in.$.tsx` and `src/routes/sign-up.$.tsx`, writes `src/start.ts` with `createStart(() => ({ requestMiddleware: [clerkMiddleware()] }))`, and seeds `.env.local`. It does not modify `src/routes/index.tsx` — your landing page still needs a sign-in affordance. See the appendix for the full file-by-file breakdown.

### Can I add Clerk to an existing TanStack Start app, or only to new scaffolds?

Existing. `clerk init` auto-detects the framework in whatever project directory you run it from. `clerk init --starter --framework tanstack-start` is the separate path for bootstrapping a brand-new template (`--starter` is a boolean that pairs with `--framework`). This article deliberately runs `@tanstack/cli create` first, then `clerk init` inside the generated app, to demonstrate the existing-project flow.

### Does the Clerk CLI support TanStack Router without Start?

The CLI's `--framework` list advertises `tanstack-start` specifically. Some frameworks (Expo, Express, Fastify) are supported for SDK installation without full scaffolding. For the current support matrix, run `clerk init --help` against your installed version — the list shifts more often than blog posts do.

### How is TanStack Start's server-side auth different from Next.js?

Different wiring point, same primitives. TanStack Start has no `middleware.ts` / `proxy.ts` convention. Clerk plugs into `createStart(() => ({ requestMiddleware: [clerkMiddleware()] }))` inside `src/start.ts`, and you call `getAuth(req)` or `auth()` from `@clerk/tanstack-react-start/server` inside server functions or loaders. Route protection happens via TanStack Router's `beforeLoad` hooks rather than Next's middleware matcher.

### Does clerk init overwrite my \_\_root.tsx?

With `-y` it rewrites the file without prompting — but it wraps your existing JSX in `<ClerkProvider>` rather than replacing it. Your layout, head config, and devtools panel survive. The only observed side-effect is cosmetic: the existing `<TanStackDevtools>` block gets dedented by two spaces and loses a trailing newline. `pnpm format` reflows it. Commit before running `clerk init` if you want a clean before/after diff.

### Do passkeys work in TanStack Start?

Yes. The client-side passkey flow is handled by Clerk's components and APIs, so it's identical across SDKs — TanStack Start, Next.js, React, or anywhere else. [Register passkeys](https://clerk.com/glossary.md#passkeys) in `<UserProfile />` on any plan. Using passkeys as a sign-in factor (the `used_for_sign_in: true` flip shown in Patch 3) requires a paid Clerk plan.

### How do I install the Clerk agent skills?

Two ways. `clerk init` ends with an `Install agent skills?` prompt that defaults to yes — accept it, or pass `-y` to auto-accept. Under the hood the CLI runs `npx skills add clerk/skills`. To install manually (or to refresh later), run that same `npx skills add clerk/skills` command at your project root. For TanStack Start, the installed set is `clerk`, `clerk-setup`, and `clerk-tanstack-patterns` — the first two are base skills, the third is selected by matching `@tanstack/react-start` in `package.json`. Agents trained on older content hallucinate the removed `<SignedIn>` / `<SignedOut>` components and the renamed `@clerk/tanstack-start` package name; the bundled skills keep them honest.

### Does clerk doctor know about TanStack Start specifics?

No Start-specific checks were observed during validation on CLI 1.0.2. `clerk doctor` validates CLI version, auth session, linked app + instance IDs, and `.env.local` contents — the things that actually break integrations. It doesn't lint your `start.ts` or route files. Describe it honestly as plumbing validation, not a framework-aware linter.

### Can I run clerk config patch against production?

Yes — pass `--instance prod`. The same patch that worked against dev will run against your prod instance. Test in dev first and `--dry-run` before committing to prod. `clerk config put --file ...` exists for environment-to-environment replication (export from dev, import to prod), but remember it's destructive — it replaces the full config, not just the keys you care about.

### Why do I see <Show when="signed-in"> instead of <SignedIn> in Clerk examples?

Clerk Core 3 (released 2026-03-03) removed `<SignedIn>`, `<SignedOut>`, and `<Protect>` and replaced them with a single `<Show>` component (`when="signed-in"`, `when="signed-out"`, or a condition callback). Older TanStack Start tutorials still show the removed API. The `npx @clerk/upgrade` codemod handles most call sites automatically. See the [Core 3 upgrade guide](https://clerk.com/docs/guides/development/upgrading/upgrade-guides/core-3.md).

### Can I edit the files clerk init generated, or do I have to keep the CLI in the loop?

Edit them freely — `clerk init`'s output is regular TypeScript with no special markers and no "ejection" step. If you want to rewrite `<ClerkProvider>` placement, the catch-all routes, or the `start.ts` middleware hook from scratch, the full SDK surface is documented at [the TanStack Start quickstart](https://clerk.com/docs/tanstack-react-start/getting-started/quickstart.md) and the source lives on [GitHub](https://github.com/clerk/javascript). The CLI is for the initial wire-up; the code afterward is yours.

## Further reading

- [TanStack Start quickstart — Clerk docs](https://clerk.com/docs/tanstack-react-start/getting-started/quickstart.md)
- [Clerk CLI reference](https://clerk.com/docs/cli.md)
- [How Clerk works — overview](https://clerk.com/docs/guides/how-clerk-works/overview.md)
- [Core 3 upgrade guide](https://clerk.com/docs/guides/development/upgrading/upgrade-guides/core-3.md)
- [TanStack Start authentication guide](https://tanstack.com/start/latest/docs/framework/react/guide/authentication)
- [Clerk CLI source — GitHub](https://github.com/clerk/cli)
