Skip to content

fix(nextjs): skip invalidateCacheAction on Next.js 15+ for all auth transitions#7873

Open
jacekradko wants to merge 1 commit intomainfrom
jacek/fix-safari-otp-impersonation
Open

fix(nextjs): skip invalidateCacheAction on Next.js 15+ for all auth transitions#7873
jacekradko wants to merge 1 commit intomainfrom
jacek/fix-safari-otp-impersonation

Conversation

@jacekradko
Copy link
Member

@jacekradko jacekradko commented Feb 18, 2026

Testing this out. Not ready for review

Summary

Fixes OTP and impersonation sign-in failing in Safari on the dashboard (Next.js 15+/16).

Root cause

invalidateCacheAction() calls cookies().delete() inside a server action during onBeforeSetActive. In Next.js 15+, this triggers an automatic re-render of the current page's RSC tree as part of the server action response. In Safari, this RSC delivery fails (TypeError: Load failed), causing Next.js to fall back to a hard browser navigation (full page reload).

For impersonation flows, the reload navigates back to /sign-in?__clerk_ticket=<token> (the URL from Next.js's internal router state, which history.replaceState doesn't update). The sign-in page re-submits the already-consumed one-time token, resulting in a 400 error ("This actor token has already been used").

For OTP flows, the hard reload loses the sign-in state, forcing the user to restart.

Fix

The server action was already skipped for sign-out on Next.js 15+ (to avoid 404/405 errors on Vercel for protected pages). This PR extends the skip to all auth state transitions on Next.js 15+, relying on router.refresh() in onAfterSetActive for cache invalidation — which is sufficient given the less aggressive router cache in Next.js 15+.

What changed

  • packages/nextjs/src/app-router/client/ClerkProvider.tsx: Removed && intent === 'sign-out' condition from the Next.js 15+ check, so invalidateCacheAction() is skipped for all auth transitions (not just sign-out)

Test plan

  • Verify OTP sign-in works on Safari (dashboard staging)
  • Verify impersonation works on Safari (dashboard staging)
  • Verify sign-out still works correctly on Next.js 15+/16
  • Verify sign-in/sign-out on Chrome (no regression)
  • Verify cache invalidation on Next.js 14 is unaffected (still calls server action)

Relates to USER-4720

Summary by CodeRabbit

Release Notes

  • Refactor

    • Optimized cache invalidation logic for Next.js 15 and 16 to align with improved cache behavior in these versions.
  • Documentation

    • Expanded comments explaining cache invalidation strategies across different Next.js versions.

…ransitions

On Next.js 15+, calling `invalidateCacheAction()` during sign-in triggers
`cookies().delete()`, which causes Next.js to re-render the current page's
RSC tree as part of the server action response. In Safari, this RSC delivery
fails ("TypeError: Load failed"), causing Next.js to fall back to a hard
browser navigation. For flows using one-time tokens (e.g. impersonation via
`__clerk_ticket`), the hard reload re-submits the spent token, resulting in
a 400 error.

The server action was already skipped for sign-out on Next.js 15+ (to avoid
404/405 errors on protected pages). This extends the skip to all auth state
transitions, relying on `router.refresh()` in `onAfterSetActive` for cache
invalidation — which is sufficient given the less aggressive router cache in
Next.js 15+.
@vercel
Copy link

vercel bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
clerk-js-sandbox Skipped Skipped Feb 18, 2026 2:24am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 18, 2026

⚠️ No Changeset found

Latest commit: ec908bf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

Modified the ClerkProvider component to refactor cache invalidation behavior in the onBeforeSetActive handler. The change introduces version-specific logic where Next.js versions 15 and 16 immediately resolve without calling invalidateCacheAction(), while earlier versions retain the existing behavior. Updated accompanying comments to explain the rationale for skipping server-action invalidation in newer versions, relying instead on onAfterSetActive for layout refresh. The unused parameter _intent was introduced alongside these modifications.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically summarizes the main change: skipping invalidateCacheAction on Next.js 15+ for all auth transitions, directly reflecting the code modification in ClerkProvider.tsx.
Linked Issues check ✅ Passed The PR addresses USER-4720 by fixing OTP/impersonation failures in Safari by preventing RSC delivery failures through conditional server action skipping on Next.js 15+, aligning with the objective to resolve the hard reload issue.
Out of Scope Changes check ✅ Passed All changes are scoped to the ClerkProvider.tsx file and directly address the issue: modifying the onBeforeSetActive handler logic and updating related comments for Next.js 15+ behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 18, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7873

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7873

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7873

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7873

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7873

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7873

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7873

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7873

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7873

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7873

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@7873

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7873

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7873

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7873

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7873

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7873

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7873

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7873

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7873

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7873

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7873

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7873

commit: ec908bf

@jacekradko
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @jacekradko - the snapshot version command generated the following package versions:

Package Version
@clerk/agent-toolkit 0.3.0-snapshot.v20260218023153
@clerk/astro 3.0.0-snapshot.v20260218023153
@clerk/backend 3.0.0-snapshot.v20260218023153
@clerk/chrome-extension 3.0.0-snapshot.v20260218023153
@clerk/clerk-js 6.0.0-snapshot.v20260218023153
@clerk/dev-cli 1.0.0-snapshot.v20260218023153
@clerk/expo 3.0.0-snapshot.v20260218023153
@clerk/expo-passkeys 1.0.0-snapshot.v20260218023153
@clerk/express 2.0.0-snapshot.v20260218023153
@clerk/fastify 2.7.0-snapshot.v20260218023153
@clerk/localizations 4.0.0-snapshot.v20260218023153
@clerk/msw 0.0.1-snapshot.v20260218023153
@clerk/nextjs 7.0.0-snapshot.v20260218023153
@clerk/nuxt 2.0.0-snapshot.v20260218023153
@clerk/react 6.0.0-snapshot.v20260218023153
@clerk/react-router 3.0.0-snapshot.v20260218023153
@clerk/shared 4.0.0-snapshot.v20260218023153
@clerk/tanstack-react-start 1.0.0-snapshot.v20260218023153
@clerk/testing 2.0.0-snapshot.v20260218023153
@clerk/ui 1.0.0-snapshot.v20260218023153
@clerk/upgrade 2.0.0-snapshot.v20260218023153
@clerk/vue 2.0.0-snapshot.v20260218023153

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/agent-toolkit

npm i @clerk/agent-toolkit@0.3.0-snapshot.v20260218023153 --save-exact

@clerk/astro

npm i @clerk/astro@3.0.0-snapshot.v20260218023153 --save-exact

@clerk/backend

npm i @clerk/backend@3.0.0-snapshot.v20260218023153 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.0.0-snapshot.v20260218023153 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.0.0-snapshot.v20260218023153 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@1.0.0-snapshot.v20260218023153 --save-exact

@clerk/expo

npm i @clerk/expo@3.0.0-snapshot.v20260218023153 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.0-snapshot.v20260218023153 --save-exact

@clerk/express

npm i @clerk/express@2.0.0-snapshot.v20260218023153 --save-exact

@clerk/fastify

npm i @clerk/fastify@2.7.0-snapshot.v20260218023153 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.0.0-snapshot.v20260218023153 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.1-snapshot.v20260218023153 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.0.0-snapshot.v20260218023153 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.0.0-snapshot.v20260218023153 --save-exact

@clerk/react

npm i @clerk/react@6.0.0-snapshot.v20260218023153 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.0.0-snapshot.v20260218023153 --save-exact

@clerk/shared

npm i @clerk/shared@4.0.0-snapshot.v20260218023153 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20260218023153 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.0-snapshot.v20260218023153 --save-exact

@clerk/ui

npm i @clerk/ui@1.0.0-snapshot.v20260218023153 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.0-snapshot.v20260218023153 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.0-snapshot.v20260218023153 --save-exact

@Ephem
Copy link
Member

Ephem commented Feb 18, 2026

I get this was probably a work in progress attempt to fix things, but it seems like the real culprit was the onAfterSetActive: #7877

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants