
Add Clerk authentication to a Next.js app with the Clerk CLI
How do I add Clerk authentication to a Next.js app with the Clerk CLI?
Run clerk init against a Next.js 16 App Router project — the Clerk CLI (released 2026-04-22) installs @clerk/nextjs v7, writes proxy.ts (the Next.js 16 middleware replacement, per the release notes), wires ClerkProvider into the layout, and scaffolds sign-in and sign-up routes. Then run clerk env pull to write your publishable key and secret key into .env.local, clerk doctor to validate the wiring, and clerk config patch to manage passkeys, sign-in methods, and password policy as code you can commit. The walkthrough below goes end-to-end against a fresh starter and shows how to inspect users with clerk api.
Before 2026-04-22, adding Clerk to Next.js meant three things in three places — install the SDK in your terminal, copy keys out of the dashboard, and hand-edit layout.tsx and middleware.ts in your editor. The CLI replaces that default path; the dashboard remains the escape hatch (clerk open) for OAuth provider client secrets, billing, and other operations that still live there.
1. Why the Clerk CLI
Before 2026-04-22, adding Clerk to a Next.js app meant three things in three places: install the SDK in your terminal, copy keys out of the Clerk Dashboard, and hand-edit layout.tsx and middleware.ts in your editor. Every "Add Clerk to Next.js" tutorial led with pnpm add @clerk/nextjs, a dashboard tab, and a block of boilerplate to paste in. The CLI replaces that round-trip.
clerk init installs the SDK, writes proxy.ts (the Next.js 16 middleware replacement, per the Next.js 16 release notes), wires ClerkProvider into the layout, and scaffolds sign-in and sign-up routes. clerk env pull writes your publishable key and secret key straight to .env.local. clerk config pull and clerk config patch let you treat Clerk's instance configuration like any other file in your repo: code-reviewable, versionable, diff-able, scriptable from CI. clerk doctor is a first-class preflight that catches missing envs, broken providers, or stale SDK versions before the dev server does. clerk api is an authenticated terminal for the Backend API so you can explore users, sessions, and organizations without hand-rolling curl commands.
The CLI does not replace the dashboard — some operations (OAuth provider client secrets, billing) still live there, and clerk open is the escape hatch. It replaces the default path: the one you hit at the start of every new project.
2. Prerequisites
Before starting, confirm you have:
Intermediate TypeScript comfort helps but is not required — the starter is a standard Next.js 16 App Router project, and the configuration patches are plain JSON.
3. Install or update the Clerk CLI
Install globally with your preferred package manager:
pnpm add -g clerknpm install -g clerkbrew install clerk/stable/clerkcurl -fsSL https://clerk.com/install | bashRunning it once without installing is also supported via npx clerk or bunx clerk, which is the right call for CI or one-shot scripts where a global install is overkill.
If you already have it, update to the latest release:
clerk updateUse clerk update --channel canary to ride the unstable channel, and clerk update --all to update every clerk binary found on your PATH — useful if you have the CLI installed via multiple package managers (say, pnpm plus Homebrew) and want them all on the same version. Agent skills update separately, via clerk skill install (see next section). Verify the install:
clerk --version4. Install the Clerk agent skill and update existing Clerk skills
Clerk ships a bundled set of agent skills — the core clerk skill for the CLI itself plus framework-specific skills like clerk-nextjs-patterns, clerk-react-patterns, and clerk-tanstack-patterns — that teach AI coding harnesses (Claude Code, Cursor, Copilot) how to use Clerk correctly. Install them once globally:
clerk skill installThe CLI detects your scope (user-level ~/.claude/skills/ by default) and installs every Clerk-maintained skill into it. If you want project-local install instead, run the command inside a project directory with a .claude/ folder present.
The skills give your agent current, version-matched Clerk knowledge — for example, they know @clerk/nextjs v7 exports <Show when="signed-in"> rather than the removed <SignedIn> / <SignedOut> components, so the code your agent generates matches the code the CLI scaffolds. This pattern — CLI-installed, version-pinned agent skills — is the same model Next.js and shadcn/ui have adopted.
5. Log in and pick an application
Authenticate the CLI with your Clerk account. This opens a browser OAuth flow and returns a token the CLI stores locally:
clerk auth login
clerk whoamiclerk whoami prints the account email and the active instance, which is how you verify the login stuck.
List the Clerk applications your account can access:
clerk apps listThe output includes each app's ID (app_…), name, and instance types. You can create a new app from the terminal with clerk apps create <name>, which is useful when scripting onboarding or spinning up a throwaway instance for testing. If you need the dashboard for anything the CLI does not cover yet — custom OAuth credentials, billing, analytics — clerk open opens the currently-linked application in your browser.
6. Scaffold a Next.js app with clerk init --starter
From the directory where you want the new project to live:
clerk init --starter --framework next --pm pnpm --name my-clerk-next-app--starter tells the CLI to scaffold a new project from a template rather than integrate into the current directory. --framework next selects the Next.js starter; --pm pnpm chooses your package manager; --name names the directory. Omit --name and the CLI prompts you interactively.
The starter generates a Next.js 16 App Router project pinned to next@16.2.4, react@19.2.4, and @clerk/nextjs@^7.2.5, with the following layout:
my-clerk-next-app/
├─ app/
│ ├─ layout.tsx # ClerkProvider, <Show when="..."> header, UserButton
│ ├─ page.tsx
│ ├─ sign-in/[[...sign-in]]/page.tsx
│ └─ sign-up/[[...sign-up]]/page.tsx
├─ proxy.ts # clerkMiddleware + createRouteMatcher
├─ next.config.ts
├─ package.json
└─ tsconfig.jsonTwo things to notice about that layout. First, the middleware file is named proxy.ts, not middleware.ts. Next.js 16 renamed the network-boundary file to proxy.ts to make the boundary explicit; the CLI's starter already uses the new name. Second, the root layout uses <Show when="signed-in"> and <Show when="signed-out"> — the Clerk Core 3 replacements for the older <SignedIn> / <SignedOut> components — so gated UI is correct out of the box.
The generated proxy.ts looks like this:
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)'])
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect()
}
})
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
}Every route is private by default; only the sign-in and sign-up paths are public. auth.protect() short-circuits unauthenticated requests before they reach any server component.
clerk init also auto-links the generated project to a Clerk application. If you ran the command signed in, it created or attached a development instance and wrote the link to .clerk/config.json in the project. That is what makes the next three commands (env pull, doctor, config) work without any extra flags.
Switch into the new directory:
cd my-clerk-next-app7. Link to an existing Clerk app (optional)
If you want to point the scaffolded project at an app you (or your team) already own — say, a shared staging instance — break the auto-link and create a new one:
clerk unlink
clerk link --app app_xxxGet the application ID from clerk apps list. clerk link writes the chosen app back to .clerk/config.json so subsequent CLI commands target it. This workflow matters most on teams: every developer can scaffold locally, then clerk link onto the shared development instance so they all hit the same user pool, same config, same logs.
You can also skip the auto-link entirely by passing --app app_xxx to clerk init in the first place, which is the right shape for CI or scripted scaffolds.
8. Pull environment variables
With a linked app in place, pull the publishable and secret keys straight into .env.local:
clerk env pullThe command writes two variables:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_…
CLERK_SECRET_KEY=sk_test_…By default clerk env pull targets the development instance. When you are ready to ship, pull production keys instead:
clerk env pull --instance prodUse --file to write somewhere other than .env.local — for example, clerk env pull --file .env.production.local for a Vercel deploy that expects that filename. The command is idempotent: re-running it overwrites the matched keys without touching any unrelated variables you have added to the file.
9. Run clerk doctor the first time (it should fail)
clerk doctor is the integration health check. Run it before you start the dev server the first time — it catches problems the dev server would otherwise report as a stack trace on the first page load.
To see the failure shape on purpose, temporarily hide the env file and run doctor:
mv .env.local .env.local.bak
clerk doctorThe output flags the missing publishable key and the missing secret key as distinct failures, confirms that ClerkProvider is wired in app/layout.tsx, confirms that clerkMiddleware is wired in proxy.ts, and reports your installed @clerk/nextjs version. The failure is specific enough to act on: it tells you which variable is missing and where the CLI expects it, not just "auth is broken."
A failing clerk doctor is a good thing. It is faster and more legible than a runtime stack trace from the Next.js dev server, and it gives you actionable errors before you have loaded a single page.
10. Run clerk doctor after env pull (green)
Restore the env file and re-run:
mv .env.local.bak .env.local
clerk doctorEvery check now passes: env vars present, provider wired, middleware wired, SDK version current. clerk doctor --spotlight hides the passing checks and shows only failures, which is the mode you want once the project is healthy and you are only running doctor to confirm nothing has regressed.
11. Start the dev server and sign up a test user
With doctor green, start the dev server:
pnpm devOpen http://localhost:3000. The starter renders a header with Sign in and Sign up buttons (the <Show when="signed-out"> branch of app/layout.tsx). Click Sign up, create a test user with email + password, verify the email code, and land back on the starter's home page — now with a UserButton avatar in the header (the <Show when="signed-in"> branch).
That is a working Clerk-authed Next.js 16 app — scaffolded, keyed, and signed into — with zero dashboard clicks. Every step after this point is configuration on top of a running system.
12. Configure Clerk as code: clerk config
clerk config is the command that made the CLI worth shipping. Instead of clicking through dashboard toggles to enable passkeys, add sign-in methods, or change session policy, you describe the configuration in JSON and apply it through the terminal — reviewable in a pull request, versionable in git, replayable across dev and production.
Discover the schema
Before patching anything, see what is configurable:
clerk config schemaThe full schema is long. Narrow it to the keys you care about:
clerk config schema --keys auth_passkey session auth_access_controlPull the current state
Snapshot the current instance configuration:
clerk config pull --output config.before.jsonconfig.before.json is a full dump of every setting Clerk currently has for the linked instance — sign-in methods, session policy, OAuth connectors, organization settings, branding. Commit it to a branch (it's not a secret; it contains no keys) and you have a versioned record of the instance.
Patch 1 — enable passkeys
Passkeys are phishing-resistant, WebAuthn-backed credentials bound to the user's device.
Dry-run the patch first to see the exact change the CLI will apply:
clerk config patch --dry-run --json '{"auth_passkey":{"used_for_sign_in":true}}'The dry-run output prints the JSON diff against the current config without mutating anything. When you are happy with it, drop --dry-run:
clerk config patch --json '{"auth_passkey":{"used_for_sign_in":true}}' --yes--yes skips the interactive confirmation so the command works in CI. Passkey sign-in is now enabled on the instance. Clerk's UI components will pick up the change on the next render — no server restart required beyond Next.js's dev HMR.
Patch 2 — tighten sign-in security
Block disposable email domains at sign-up and lower the failed-attempt lockout threshold from the default:
clerk config patch --dry-run --json '{
"auth_access_control": { "block_disposable_email_domains": true },
"auth_attack_protection": {
"user_lockout": { "max_attempts": 10 }
}
}' --yesAfter the dry-run confirms the diff, remove --dry-run. Two semantic changes in one patch: sign-ups from throwaway email services are rejected, and the lockout triggers after 10 failed attempts instead of the default 100.
Patch 3 — strengthen the password policy
Clerk's default policy adheres to NIST 800-63B — 8-character minimum, no composition rules, HIBP (Have I Been Pwned) checks on sign-up and sign-in — which is sound out of the box. If your threat model calls for something stricter, tighten the policy so sign-ups and password resets must meet a higher bar:
clerk config patch --json '{
"auth_password": {
"min_length": 12,
"min_zxcvbn_strength": 3,
"require_numbers": true,
"show_zxcvbn": true
}
}' --yesFour semantic changes: the minimum length goes from the default 8 to 12 characters, the min_zxcvbn_strength threshold lifts from 0 to 3 (on zxcvbn's 0-4 scale, where 3 is "safely unguessable"), passwords must contain at least one number, and Clerk's hosted UI renders a live strength meter on the password field. HIBP checks are already enforced on sign-in by default on a fresh instance — see auth_password.enforce_hibp_on_sign_in in the pulled config.
Snapshot the result
Pull the new state and diff it against the original:
clerk config pull --output config.after.json
diff config.before.json config.after.jsonYou should see the expected semantic changes (plus an auto-bumped config_version): auth_passkey.used_for_sign_in went true, auth_access_control.block_disposable_email_domains went true, auth_attack_protection.user_lockout.max_attempts went 10, and the four auth_password tightening fields moved from their defaults to the stricter policy. That diff is now your instance configuration change log — commit both files to the repo and the review is a normal PR.
13. Verify the config changes worked
Reload http://localhost:3000. Click Sign in and you should now see a passkey registration option (Clerk's SDK renders the passkey button when auth_passkey.used_for_sign_in is true and the browser supports WebAuthn). Sign in with your existing test user, open User button → Manage account → Security, and register a passkey using your browser's built-in authenticator — Touch ID on macOS, Windows Hello on Windows, the device's screen lock on Android. The passkey is now bound to that user and can be used on any subsequent sign-in.
Try to sign up a new user with a disposable email (for example, an address at mailinator.com) and you should get a policy-enforced rejection from the auth_access_control patch. In the same sign-up flow, try a weak password like password — the form will reject it against the new min_length, min_zxcvbn_strength, and require_numbers rules, and the strength meter underneath the field reflects the show_zxcvbn toggle from Patch 3.
14. Inspect your instance with clerk api
clerk api is an authenticated terminal client for Clerk's Backend API, with key resolution and instance targeting handled automatically by the linked app.
Discover what endpoints exist:
clerk api ls
clerk api ls usersclerk api ls lists the top-level resources; clerk api ls users scopes to a single resource and shows the available operations.
Fetch the list of users on the instance — your test user should appear:
clerk api /usersPick the user ID from the response and fetch the full user object:
clerk api /users/user_xxxThe response includes the passkeys array (populated after step 13) and the user's primary email, both of which confirm the earlier steps landed. Use the one-liner clerk api /users/user_xxx | jq '.passkeys' to zoom in.
For write operations, clerk api supports -X POST, -X PATCH, and -X DELETE with a -d payload and optional --dry-run. Mutations in a tutorial are a footgun, so the one you will run first in production is almost always a read: users, sessions, organizations. The Backend API reference documents the full surface and is the right place to look before running a mutation.
Clerk also ships a separate Platform API for account-level operations (list your own applications, manage billing). Inspect it with:
clerk api --platform lsPlatform endpoints require platform-level credentials, which are distinct from per-application keys — clerk api --platform resolves them from your logged-in account rather than the linked application.
15. CLI reference (quick skim)
Commands covered above, plus the ones you will reach for next:
# Setup
clerk --version
clerk update # latest stable
clerk update --channel canary # ride the canary train
clerk update --all # update every clerk install on PATH
clerk completion zsh # emit shell completion for zsh|bash|fish
# Auth
clerk auth login
clerk whoami
clerk open # dashboard escape hatch
# Apps
clerk apps list
clerk apps create <name>
clerk link --app app_xxx
clerk unlink
# Project
clerk init # integrate into current dir
clerk init --starter # new project from template
clerk init --prompt # emit setup instructions for agents
# Environment + diagnostics
clerk env pull [--instance prod] [--file path]
clerk doctor [--spotlight] [--fix] [--verbose]
# Config as code
clerk config schema [--keys a b c]
clerk config pull [--output file]
clerk config patch --json '...' [--dry-run] [--yes]
clerk config put --file config.json # destructive — see warning below
# Backend API
clerk api ls [resource]
clerk api <path>
clerk api --platform ls
clerk api -X POST <path> -d '...' # mutations; pair with --dry-run
# Agent skills
clerk skill installFor the full reference including per-command flags, see the canonical CLI documentation and the clerk/cli repository.