Skip to main content
Docs

Use this pre-built prompt to upgrade your project with AI assistance.

Overview

Core 3 focuses on consistency and cleanup across Clerk's SDKs. Most projects can be upgraded in under 30 minutes using the upgrade CLI.

Here's a summary of what changed:

  • Component consolidation: Protect, SignedIn, and SignedOut are replaced by a single Show component.
  • Package renaming:
    • @clerk/clerk-react becomes @clerk/react.
    • @clerk/clerk-expo becomes @clerk/expo.
    • createTheme moves to @clerk/ui/themes/experimental.
    • Types consolidate under @clerk/shared/types.
  • Appearance updates: appearance.layout is now appearance.options, showOptionalFields defaults to false, and color variables apply at full opacity.
  • Behavior alignment: Legacy redirect props and billing flags are removed in favor of the newer patterns.
  • Token handling: getToken() now throws ClerkOfflineError when offline and uses proactive background refresh for better performance.
  • Platform requirements:
    • For all packages: Node.js 20.9.0+ is required.
    • For @clerk/nextjs: Next.js 15.2.3+ is required.
  • Satellite apps: Satellite apps no longer perform automatic redirects on first visit.

In addition to the cross-SDK changes listed above, each SDK has its own specific changes. See the SDK-specific changes section for details on your framework.

Preparing to upgrade

Before upgrading, make sure your project meets the following requirements:

  • Node.js 20.9.0 or later is installed. You can check your version by running node -v in your terminal. If you need to update, see the Node.js download page.
  • Your Clerk SDKs are on the latest Core 2 version. Updating to the latest Core 2 release first allows you to resolve deprecation warnings incrementally, which will make the Core 3 upgrade smoother. For example, for Next.js, run npm install @clerk/nextjs@6.

Upgrade using the CLI

The recommended way to upgrade is to use the @clerk/upgrade CLI. This tool will scan your project, detect the breaking changes that affect your codebase, and apply fixes automatically where possible.

To run the CLI, open your terminal, navigate to your project directory, and run the command that matches your package manager:

terminal
npx @clerk/upgrade
terminal
pnpm dlx @clerk/upgrade
terminal
yarn dlx @clerk/upgrade
terminal
bun x @clerk/upgrade

The CLI will walk you through the changes it detects. For most projects, this will handle the majority of the upgrade automatically.

Note

The upgrade CLI modifies .ts, .tsx, .js, and .jsx files. If you're using Astro, you'll need to manually update your .astro template files — see the SDK-specific changes section below.

The rest of this guide covers each breaking change in detail, which is useful as a reference for changes the CLI can't fully automate or if you'd like to understand what changed and why.

Breaking changes

The following breaking changes apply to all Clerk SDKs.

Deprecation removals

The following deprecated APIs have been removed from all Clerk SDKs.

SDK-specific changes

The following changes only apply to specific SDKs. Select your SDK below for additional upgrade steps.

Encryption key required when passing secretKey at runtime

When passing secretKey as a runtime option to clerkMiddleware(), you must now also provide a CLERK_ENCRYPTION_KEY environment variable.

Add the encryption key to your environment:

CLERK_ENCRYPTION_KEY=your-encryption-key

See the clerkMiddleware documentationNext.js Icon.

ClerkProvider should be inside <body> for Next.js 16 cache components

For Next.js 16 cache components support (cacheComponents: true), ClerkProvider must be positioned inside <body> rather than wrapping <html>. This prevents "Uncached data was accessed outside of <Suspense>" errors.

<ClerkProvider>
  <html lang="en">
    <body>{children}</body>
  </html>
</ClerkProvider>
<html lang="en">
  <body>
    <ClerkProvider>
      {children}
    </ClerkProvider>
  </body>
</html>

If you're using Next.js 16 with cacheComponents: true, you may also need to wrap ClerkProvider in a <Suspense> boundary.

Minimum Next.js version increased to 15.2.3

Support for Next.js 13 and 14 has been dropped. @clerk/nextjs now requires next@>=15.2.3.

{
  "dependencies": {
    "next": "^14.0.0",
    "next": "^15.2.3"
  }
}

See the Next.js upgrade guide for help migrating your application.

auth.protect() returns 401 instead of 404 for unauthenticated server actions

auth.protect() in clerkMiddleware() now returns a 401 Unauthorized response instead of a 404 Not Found when an unauthenticated request is made from a server action. If you have client-side error handling that checks for 404 responses from server actions when the user is signed out, update it to handle 401 instead:

try {
  await myServerAction();
} catch (error) {
  if (error.status === 404) {
  if (error.status === 401) {
    // Handle unauthenticated user
  }
}

No changes are required if you are not explicitly checking the HTTP status code in your error handling.

@clerk/clerk-react renamed to @clerk/react

The @clerk/clerk-react package has been renamed to @clerk/react.

Update your imports:

import { ClerkProvider, useUser } from '@clerk/clerk-react'
import { ClerkProvider, useUser } from '@clerk/react'

And update your package.json:

"@clerk/clerk-react": "^5.0.0",
"@clerk/react": "^7.0.0",

@clerk/react-router/api.server export removed

The @clerk/react-router/api.server export has been removed. Use @clerk/react-router/server instead:

import { getAuth } from '@clerk/react-router/api.server'
import { getAuth } from '@clerk/react-router/server'

rootAuthLoader requires clerkMiddleware()

rootAuthLoader now requires clerkMiddleware() to be installed. Using rootAuthLoader without middleware will throw a runtime error.

  1. Enable the v8_middleware future flag:
react-router.config.ts
export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config
  1. Use clerkMiddleware() alongside rootAuthLoader:
import { clerkMiddleware, rootAuthLoader } from '@clerk/react-router/server'

export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()]

export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args)

Upgrade CLI does not auto-fix .astro template files

The upgrade CLI will fix .ts/.tsx files (React islands), but .astro template files must be updated manually. Components are imported from @clerk/astro/components:

---
import { SignedIn, SignedOut, Protect } from '@clerk/astro/components'
import { Show } from '@clerk/astro/components'
---

<SignedIn>
<Show when="signed-in">
    <Dashboard />
</SignedIn>
</Show>

<SignedOut>
<Show when="signed-out">
    <SignInPage />
</SignedOut>
</Show>

<Protect role="admin">
<Show when={{ role: "admin" }}>
    <AdminPanel />
</Protect>
</Show>

as prop removed from button components

The deprecated as prop has been removed from unstyled button components (SignInButton, SignUpButton, SignOutButton, CheckoutButton, PlanDetailsButton, SubscriptionDetailsButton). Use the asChild prop with a custom element in the default slot instead:

<SignInButton as="a" href="/sign-in"> Sign in </SignInButton>
<SignInButton asChild>
  <a href="/sign-in">Sign in</a>
</SignInButton>

Runtime environment variables now take precedence over build-time values

Environment variable resolution in @clerk/astro now prefers process.env over import.meta.env. This means runtime environment variables (e.g., set in the Node.js adapter or container) take precedence over values statically replaced by Vite at build time.

The new resolution order is:

  1. locals.runtime.env (Cloudflare Workers)
  2. process.env (Node.js runtime)
  3. import.meta.env (Vite build-time static replacement)

If you rely on build-time PUBLIC_* values that differ from your runtime process.env, update your configuration to ensure the correct values are set in process.env at runtime.

Minimum TanStack version increased

@clerk/tanstack-react-start v1 requires @tanstack/react-start v1.157.0 or later (along with matching versions of @tanstack/react-router and @tanstack/react-router-devtools).

If you pin TanStack versions, update them:

{
  "dependencies": {
    "@tanstack/react-router": "1.154.14",
    "@tanstack/react-router-devtools": "1.154.14",
    "@tanstack/react-start": "1.154.14",
    "@tanstack/react-router": "1.160.2",
    "@tanstack/react-router-devtools": "1.160.2",
    "@tanstack/react-start": "1.160.2"
  }
}

@clerk/clerk-expo renamed to @clerk/expo

The @clerk/clerk-expo package has been renamed to @clerk/expo.

Update your imports:

import { ClerkProvider, useUser } from '@clerk/clerk-expo'
import { ClerkProvider, useUser } from '@clerk/expo'

And update your package.json:

"@clerk/clerk-expo": "^2.0.0",
"@clerk/expo": "^3.0.0",

Clerk export removed

The Clerk export has been removed from @clerk/expo. Use useClerk() hook instead:

import { Clerk } from '@clerk/expo'
import { useClerk } from '@clerk/expo'

Clerk.signOut()
const { signOut } = useClerk()
signOut()

Minimum Expo version increased to 53

@clerk/expo v3 requires Expo SDK 53 or later.

"expo": "~52.0.0",
"expo": "~53.0.0",

See the Expo upgrade guide for help migrating your application.

useSignInWithApple and useSignInWithGoogle moved to separate entry points

The useSignInWithApple and useSignInWithGoogle hooks have been moved to dedicated entry points to avoid bundling optional dependencies:

import { useSignInWithApple } from '@clerk/expo'
import { useSignInWithApple } from '@clerk/expo/apple'

import { useSignInWithGoogle } from '@clerk/expo'
import { useSignInWithGoogle } from '@clerk/expo/google'

publishableKey prop required in ClerkProvider

The publishableKey prop is now required in ClerkProvider for Expo apps. Previously, it would fall back to EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY, but environment variables inside node_modules are not inlined during production builds in React Native/Expo, which could cause apps to crash in production.

const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!;

<ClerkProvider>
<ClerkProvider publishableKey={publishableKey}>
  {/* Your app */}
</ClerkProvider>

getAuth() helper removed

The getAuth() helper has been removed. Use auth() instead:

import { getAuth } from '@clerk/nuxt/server'
import { auth } from '@clerk/nuxt/server'

const { userId } = getAuth(event)
const { userId } = auth()

Routing strategy now defaults to path

The default routing strategy for Nuxt has changed from hash to path. If you were relying on hash-based routing, explicitly set the strategy:

// nuxt.config.ts
export default defineNuxtConfig({
  clerk: {
    routingStrategy: 'hash', // Restore Core 2 behavior
  },
})

enableHandshake option removed

The enableHandshake option had no effect and has been removed from clerkMiddleware:

app.use(clerkMiddleware({ enableHandshake: false }))
app.use(clerkMiddleware())

req.auth object-access removed

Accessing req.auth as a plain object (legacy clerk-sdk-node style) is no longer supported. Use getAuth() instead:

const { userId } = req.auth
const { userId } = getAuth(req)

Feedback

What did you think of this content?

Last updated on