javascript icon indicating copy to clipboard operation
javascript copied to clipboard

add signOutCallback to UserButton

Open jescalan opened this issue 2 months ago • 7 comments

Description

Clerk's signOut method offers a signOutCallback param which can be used for redirects, but can also be used for anything else a developer needs to do or clean up when signing a user out, such as cleaning up local storage, running analytics events, etc.

Previously, we had a prop on UserButton that allowed passing in a custom function in this way, but it was removed in Core 2 under the premise that it was only really used for redirecting after sign out and providing an after sign out URL prop would add simplicity and consistency with other component APIs.

I think this was a poorly considered change, as there are a variety of other things that developers could want to do on sign out and this is no longer possible unless you build your own UserButton component. We have received customer feedback confirming this need.

This PR re-introduces the signOutCallback parameter to UserButton, but without changing anything else. If both signOutCallback and afterSignOutUrl are passed, signOutCallback will override the afterSignOutUrl redirect behavior.

Checklist

  • [ ] pnpm test runs as expected.
  • [ ] pnpm build runs as expected.
  • [ ] (If applicable) JSDoc comments have been added or updated for any package exports
  • [ ] (If applicable) Documentation has been updated

Type of change

  • [ ] 🐛 Bug fix
  • [x] 🌟 New feature
  • [ ] 🔨 Breaking change
  • [ ] 📖 Refactoring / dependency upgrade / documentation
  • [ ] other:

Summary by CodeRabbit

  • New Features
    • UserButton now supports a signOutCallback prop to customize post-sign-out behavior, allowing developers to override default navigation with custom handlers for both single and multi-session scenarios.

jescalan avatar Oct 16 '25 14:10 jescalan

⚠️ No Changeset found

Latest commit: ab20bf34f5818a3a6dda3ce06d86bfd85e23f1d9

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

changeset-bot[bot] avatar Oct 16 '25 14:10 changeset-bot[bot]

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

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Oct 16, 2025 2:25pm

vercel[bot] avatar Oct 16 '25 14:10 vercel[bot]

Walkthrough

This PR adds support for a customizable signOutCallback prop to the UserButton component, enabling developers to override default post-sign-out navigation. Type definitions clarify callback return types by changing from void to undefined. The context layer conditionally invokes the callback or falls back to existing navigation behavior. Tests validate callback behavior in single and multi-session scenarios.

Changes

Cohort / File(s) Summary
Type definitions
packages/types/src/clerk.ts
Added signOutCallback?: SignOutCallback to UserButtonProps. Updated return types of BeforeEmitCallback, SetActiveNavigate, SignOutCallback, and CustomNavigation from void to undefined for consistency.
Component context
packages/clerk-js/src/ui/contexts/components/UserButton.ts
Integrated signOutCallback extraction from context. Navigation functions now conditionally use signOutCallback if provided, otherwise fall back to default navigation behavior (navigateAfterSignOut and navigateAfterMultiSessionSingleSignOut updated).
Tests
packages/clerk-js/src/ui/components/UserButton/__tests__/UserButton.test.tsx
Added test suites for signOutCallback behavior covering single-session sign-out, multi-session sign-out, and per-session sign-out scenarios. Tests verify callback invocation, Clerk signOut calls, and that navigation is not performed when callback is provided.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UserButton
    participant Context
    participant Callback
    participant Clerk

    User->>UserButton: Trigger sign out
    UserButton->>Context: navigateAfterSignOut called
    
    alt signOutCallback provided
        Context->>Callback: Invoke signOutCallback
        Callback->>Clerk: Custom logic (e.g., redirect)
        Note over Callback,Clerk: Navigation customized
    else signOutCallback not provided
        Context->>Clerk: redirectWithAuth (default navigation)
        Note over Context,Clerk: Default navigation behavior
    end

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

The changes follow a straightforward pattern: adding optional callback support with backward compatibility. Logic is localized and well-tested. The main review effort involves verifying callback integration points and ensuring the fallback behavior remains intact.

Poem

🐰 A rabbit hops through sign-out flows,
With callbacks now to guide where one goes,
No nav by default—just custom control,
Types tightened up to achieve the goal,
~Thump thump!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely summarizes the primary change by stating that the signOutCallback prop is being added to the UserButton component, which aligns precisely with the pull request’s main objective.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch je.add-after-sign-out-callback-to-userbutton

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

coderabbitai[bot] avatar Oct 16 '25 14:10 coderabbitai[bot]

Open in StackBlitz

@clerk/agent-toolkit

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

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

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

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

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

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

npm i https://pkg.pr.new/@clerk/elements@7006
@clerk/clerk-expo

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

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

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

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

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

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

npm i https://pkg.pr.new/@clerk/nuxt@7006
@clerk/clerk-react

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

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

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

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

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

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

npm i https://pkg.pr.new/@clerk/themes@7006
@clerk/types

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

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

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

commit: ab20bf3

pkg-pr-new[bot] avatar Oct 16 '25 14:10 pkg-pr-new[bot]

@jescalan

UserButton's afterSignOutUrl has been marked for deprecation, indicating this should be handled globally. Could the new function live in ClerkProvider ?

  /**
   * Full URL or path to navigate to after sign out is complete
   * @deprecated Configure `afterSignOutUrl` as a global configuration, either in `<ClerkProvider/>` or in `await Clerk.load()`.
   */ 

I'd strongly argue that the signOutCallback on signOut should only be used for navigation purposes. If a developer is using Clerk.signOut() they should navigate in callback, and then await for Clerk.signOut() to resolve and run any code they want afterwards.

Something to consider is that by adding a function as a prop, the developer would need to refactor to use our component/provider inside a client component, since functions as props are not allowed in server components.

panteliselef avatar Oct 16 '25 17:10 panteliselef

I stumbled across this and have some questions.

@jescalan What's the usecases that has come up? I can imagine things like firing off analytics events, but curious if we've heard others?

UserButton's afterSignOutUrl has been marked for deprecation, indicating this should be handled globally. Could the new function live in ClerkProvider ?

Something to consider is that by adding a function as a prop, the developer would need to refactor to use our component/provider inside a client component, since functions as props are not allowed in server components.

@panteliselef Aren't these statements incompatible in the case of Next? The provider is and needs to be a server component, so we can't pass in a function there? I might be misunderstanding or missing something which is why I wanted to ask.

Ephem avatar Oct 21 '25 07:10 Ephem

Analytics events and "cleanup of local data" from the user that submitted the request, which is reasonable. If they have stuff they put in local storage for example, that they'd want to flush out on sign out, this would make sense

jescalan avatar Nov 14 '25 19:11 jescalan