Skip to main content
Articles

Add Clerk authentication to a React app with the Clerk CLI

Author: Roy Anger
Published: (last updated )

This is Part 1 of a two-part series on adding Clerk authentication to a React application using the Clerk CLI. This part covers the CLI installation, scaffolding the Vite React app, and getting the dev server running. Part 2 covers React SPA-specific patterns and managing the Clerk instance as code.

How do I add Clerk authentication to a React app with the Clerk CLI?

Run clerk init --starter --framework react to scaffold a Vite + React 19 single-page app with @clerk/react v6, <ClerkProvider> wired in main.tsx, <Show when="signed-in"> gating, and populated environment variables — one command, no dashboard round-trip. Then run clerk env pull to write keys, clerk doctor to verify the setup, and clerk config patch to enable passkeys, sign-in methods, session lifetime, and lockout policy as version-controllable JSON. The walkthrough below covers the full sequence end-to-end, signing up a real user, and inspecting the result with clerk api.

Not covered here: building your own backend to verify Clerk-issued tokens, React Router or TanStack Router integration (the CLI starter ships neither), and Next.js App Router patterns (the Next CLI article covers those). If you need server-side rendering or middleware-based route protection, Next.js or TanStack Start is the better starting point.

Why the Clerk CLI for React

React is the broadest surface Clerk ships for. The pre-CLI path — install @clerk/react, hand-wire <ClerkProvider> in main.tsx, paste keys from a dashboard tab into .env.local, and hope you got the provider placement right — worked, but it was fragile. Every tutorial had to re-explain the wiring, and AI coding agents couldn't reliably reproduce the setup without stepping out to a browser.

The CLI changes the answer. clerk init --starter --framework react scaffolds a Vite + React 19 project with @clerk/react v6, a wired <ClerkProvider>, <Show when="signed-in"> gating, and populated environment variables. One command. The same CLI also unlocks three things the dashboard-era tutorials never could:

  • clerk config patch — apply an instance configuration diff as reviewable JSON, with --dry-run and --yes flags so changes flow through code review, not a toggle.
  • clerk api — call any Backend or Platform API endpoint with your authenticated context already resolved.
  • clerk doctor — pre-flight your local setup and surface missing keys, drifted skill versions, or configuration mismatches.

Taken together, the CLI is the first Clerk surface that treats a React app setup as a scriptable, auditable pipeline. That is the shape AI coding agents need and the shape a serious team wants in CI. This guide assumes you have decided a client-rendered React SPA is the right shape for your product. If you are not sure — or you know you will need server-side sessions, middleware, or server-only secrets in route handlers — jump to the Next.js or TanStack Start entries in this cluster before you start.

Prerequisites

You need:

Checklist

You do not need Git initialized, a deployment target, or OAuth provider credentials to follow this walkthrough. OAuth social providers work in development with Clerk's shared credentials; production OAuth requires your own credentials, which the "Pull environment variables" section flags.

Install or update the Clerk CLI

The Clerk CLI README advertises two install methods: a Homebrew tap for macOS and Linux, and an npm package that works anywhere Node.js is installed (including Windows). Pick one — whichever you pick, updates happen in-place with clerk update:

terminal
# macOS and Linux (Homebrew tap)
brew install clerk/stable/clerk
terminal
# Any platform with Node.js 20+ (cross-platform, including Windows)
npm install -g clerk

The Homebrew tap lives at github.com/clerk/homebrew-stable and pulls the matching prebuilt binary from the CLI's GitHub releases. The npm package is published as clerk (not @clerk/cli) — a small wrapper that downloads the right native binary on install. Both paths land the same clerk executable on your PATH.

Verify the install and update if needed:

clerk --version
clerk update --yes

Anything older than the 2026-04-22 release lacks clerk init, clerk config, clerk skill, and the --mode agent flag. clerk update --yes upgrades to the latest stable; clerk update --channel canary opts into pre-release builds if you want to track fixes between stable releases.

Tip

clerk doctor is a good habit. Run it any time the CLI or your project feels off — it checks authentication state, env files, skill currency, and more in one pass. clerk doctor --spotlight formats the output for pasting into a bug report.

Install the Clerk agent skill and update existing Clerk skills

If you are using an AI coding agent (Claude Code, Codex, Aider, Gemini CLI, or similar), install the Clerk skill bundle once per machine:

clerk skill install -y

This installs Clerk-maintained agent skills into your global agent config (for Claude Code, ~/.claude/skills/). The bundle covers framework-specific guidance for React, Next.js, TanStack Start, Expo, Astro, and others, plus shared skills like clerk-setup, clerk-webhooks, and clerk-custom-ui. The agent loads the right skill by topic, so you do not have to remember names.

Note

Skills drift as Clerk ships. The content your agent sees is cached locally; new features, deprecations, and breaking changes only surface after a skill update. Re-run clerk skill install -y after each Clerk major release (Core 3 in March 2026 is the most recent) and any time an agent-generated snippet looks suspicious. clerk doctor calls out out-of-date skills when it sees them.

If you have hand-authored Clerk skills from earlier experiments, review them after the install — the bundled skills supersede the stale ones, and keeping both causes the agent to mix old and new APIs in the same file.

Log in and pick an application

The CLI holds credentials in your OS keychain after a browser-based OAuth flow:

clerk auth login
clerk whoami

clerk auth login opens your browser to Clerk, you approve the CLI, and the token lands in the keychain. clerk whoami prints your email and which application is currently selected. If no app is selected, the next command asks you to pick one:

clerk apps list

The list shows each app's human name and app_… ID. You will pass the ID to clerk init (or clerk link) in the next sections — copy it.

Scaffold a React app with clerk init --starter

From an empty parent directory, run:

clerk init --starter --framework react --pm pnpm

The CLI prompts for a project name (defaults to my-clerk-react-app), creates the directory, installs dependencies, links the project to a Clerk application (prompting you to pick from clerk apps list or to create a new one), and writes .env.local.

Here is what lands in the directory:

my-clerk-react-app/
├── .env.local               # VITE_CLERK_PUBLISHABLE_KEY + CLERK_SECRET_KEY
├── index.html
├── package.json             # vite ^8.0, react ^19.2, @clerk/react ^6.4
├── src/
│   ├── App.tsx              # sample page
│   ├── main.tsx             # <ClerkProvider> wrapper, <Show> gating
│   └── ...
├── tsconfig.json
└── vite.config.ts

Three things matter for this guide:

  1. The underlying tool is Vite 8. Dev server runs on port 5173 by default, env vars use the VITE_ prefix, and pnpm build runs tsc -b && vite build. If you are familiar with Vite, this is the same Vite.
  2. The Clerk package is @clerk/react, not @clerk/clerk-react. Core 3 (March 2026) renamed the SPA SDK. Legacy snippets that import from @clerk/clerk-react predate Core 3 and will not work against the current types.
  3. The starter ships no router and no protected route. It is a single-page App.tsx with <Show when="signed-in"> / <Show when="signed-out"> gating the header. Adding a router (React Router, TanStack Router) is your call and out of scope for the CLI scaffold.

Note

Passing --pm pnpm tells the CLI to run pnpm install, but the generated lockfile for the React starter is still package-lock.json at the time of writing. pnpm dev, pnpm build, and pnpm install all work because package.json scripts are generic — the lockfile name is cosmetic, not functional. If this bothers you, delete package-lock.json and run pnpm install to regenerate pnpm-lock.yaml.

If clerk init linked you to the wrong application, or you want to point the project at production later, unlink and relink:

clerk unlink
clerk link --app app_xxx

clerk unlink clears the local link (in .clerk/config.json inside the project). clerk link --app <id> associates the project with a different application. Re-running clerk env pull after a relink rewrites .env.local with the new app's keys, so the dev server picks up the change on next restart.

Linking is per-project, not per-machine. A single machine can have multiple projects pointed at different apps (staging, prod, per-client instances) without interference.

Pull environment variables

The starter already pulled env vars during clerk init. If you later add a teammate, move to a new machine, or rotate a key, pull again:

clerk env pull
cat .env.local

Expected output:

VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

VITE_CLERK_PUBLISHABLE_KEY is the client-exposed publishable key. Vite inlines every variable prefixed with VITE_ into the client bundle at build time, which is expected for this key — publishable keys identify your Clerk instance to the Frontend API and carry no authority beyond that.

CLERK_SECRET_KEY is the server-side secret key. Vite does not expose unprefixed variables to the client bundle, so it stays out of your browser-shipped JavaScript. The CLI writes it anyway so the same .env.local works when (not if) you add a backend service or use tooling like clerk doctor. Treat the presence of the secret key in the file as a placeholder for the future, not as an invitation to read it from React code.

Warning

Never rename CLERK_SECRET_KEY to VITE_CLERK_SECRET_KEY. Doing so tells Vite to inline the secret into the client bundle, which means anyone who loads your site can read it in the browser devtools. A 2024 write-up of a real breach (Sprocket Security) traced full CI/CD compromise to exactly this Vite misconfiguration on an unrelated app. The VITE_ prefix is the only meaningful client/server boundary in a Vite project — do not cross it.

If you need production keys on a CI server or in a deploy pipeline, pass --instance prod to clerk env pull. Development and production keys are different and non-overlapping; the CLI surfaces whichever one the linked application's instance selector points at.

Run clerk doctor the first time (it should fail)

Before you start the dev server, prove the safety net works. Move the env file out of the way and run:

mv .env.local .env.local.bak
clerk doctor

Expected output:

! No .env.local or .env file found
    Run `clerk env pull` to create one with your Clerk keys.

clerk doctor is doing its job: it spotted the missing env file before the dev server would have handed you an opaque "publishable key is required" error. Two unrelated warnings may show up in the same output (missing ~/.clerk/config.json, shell completion not installed) — both are noisy-but-not-broken and do not affect the app.

Tip

Whenever the dev server fails to render Clerk UI, try clerk doctor before opening devtools. Missing env files, drifted skill versions, an unselected application, and @clerk/react version mismatches all surface here first.

Run clerk doctor after env pull (green)

Restore the env file and re-run:

mv .env.local.bak .env.local
clerk doctor

Expected output:

✓ .env.local contains VITE_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY (development instance)

Green. Now you can start the dev server.

Start the dev server and sign up a test user

Run:

pnpm dev

Vite prints a URL like http://localhost:5173/. Open it. The starter renders a header with a sign-in button and a sign-up button. Click Sign up, enter an email address, complete the one-time code, and the header flips to show <UserButton /> — Clerk's prebuilt account menu with sign-out, account management, and (if enabled) organization switching.

A real sign-up creates a real user in your Clerk development instance. You can confirm with clerk api /users from the project directory — the new user's record comes back as JSON.

Important

If the starter's main.tsx reads publishableKey implicitly from the environment and you hit a TypeScript build error like TS2741: Property 'publishableKey' is missing, pass the prop explicitly:

import { createRoot } from 'react-dom/client'
import { ClerkProvider } from '@clerk/react'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
    <App />
  </ClerkProvider>,
)

The provider auto-reads the env var at runtime, but the TypeScript prop is non-optional in the current types. The explicit form works everywhere and is safer to copy into your own main.tsx.

Next steps

You now have a running Vite React application with Clerk authentication wired in. In Part 2, we will cover React SPA-specific auth patterns like route gating, and show how to configure your Clerk instance as code using clerk config patch.

FAQ

In this series

  1. Add Clerk authentication to a React app with the Clerk CLI (you are here)
  2. Add Clerk authentication to a React app with the Clerk CLI - Part 2