Skip to content

useAuth() return object identity churn forces Convex/React re-render cascades #10413

Open

Description

@luchillo17

Preliminary Checks

Reproduction

https://github.com/luchillo17/clerk-useauth-identity-repro

Publishable key

pk_test_cmVzb2x2ZWQtZ2xpZGVyLTYuY2xlcmsuYWNjb3VudHMuZGV2JA

Description

useAuth() from @clerk/nextjs frequently returns a new object reference even when session / org / user identity primitives are unchanged. That breaks referential equality for anyone who depends on the whole auth object (Convex’s Clerk bridge, useMemo/useCallback(..., [auth]), context providers that spread auth).

This showed up after upgrading to @clerk/nextjs ^7.9.x (felt ~1–2 weeks after our 2026-10-03…2026-10-06 bump; not present ~1 month earlier on older Clerk).

Steps to reproduce (integration shape):

  1. Next.js App Router + @clerk/nextjs + Convex ConvexProviderWithClerk (or ConvexProviderWithAuth) wired with Clerk useAuth / a wrapper that returns or spreads the Clerk auth object.
  2. Sign in with an active organization; idle on an authenticated shell (header + sidebar + heavy child such as a data table).
  3. Watch React Profiler while Clerk performs session/token ticks (no navigation, no org switch).

Expected behavior:

  • When isLoaded, isSignedIn, userId, sessionId, orgId, orgRole (etc.) are unchanged, useAuth() keeps a stable object reference, or Clerk documents stable selectors / field subscriptions so adapters need not depend on a new object every tick.
  • Transient token-refresh flags (e.g. isRefreshing) should not force a full auth-object identity change that resets Convex auth or re-renders every subscriber’s subtree without an opt-in.

Actual behavior:

  • useAuth() often yields a new object with the same identity fields.
  • If the app (or Convex’s adapter) memoizes on [auth] / [useAuth] prop identity, Convex auth can transition to loading and re-run fetchAccessToken (https://docs.convex.dev/api/modules/react), re-rendering the authenticated tree (shell + main).
  • Even with a stable useAuth function prop, spreading the Clerk auth object / reacting to isRefreshing still updates Convex auth context and multiple Clerk subscribers in one commit. We measured React Profiler commit roots under ConvexProviderWithAuth cascading into sidebar, session gate, and a large table (~80ms class work on our pages).

Why consumers cannot only destructure primitives:

  1. Convex’s official Clerk integration takes a useAuth hook and builds an adapter from its return value — object identity matters at the bridge, not only in leaf UI.
  2. useCallback / useMemo with [auth] rebuild whenever the return reference changes.
  3. isRefreshing and similar fields change without changing sessionId/orgId, still producing a new object and notifying dependents.

Ask: Please restore / guarantee stable referential equality on the useAuth() return value when identity fields are unchanged, or ship documented stable selectors for adapter authors.

Related (not a duplicate): #201 (getToken identity per render, closed 2023). This report is about the entire return object / refresh-driven churn with Convex and React 19.

Environment

Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: /home/carlos/.local/share/pnpm/store/v11
  Virtual store is at:             ../../../.cache/pnpm/dlx/966750f75ec0f0014a3ee4d556992040/mvkdzm7i-uc0/node_modules/.pacquet
.../mvkdzm7i-uc0                         | Progress: resolved 1, reused 0, downloaded 1, added 1, done

dependencies:
+ envinfo 7.21.0

  System:
    OS: Linux 6.18 Ubuntu 24.04.5 LTS 24.04.5 LTS (Noble Numbat)
    CPU: (24) x64 13th Gen Intel(R) Core(TM) i9-13900KF
    Memory: 4.79 GB / 15.51 GB
    Container: Yes
    Shell: 5.9 - /usr/bin/zsh
  Binaries:
    Node: 24.21.0 - /home/carlos/.local/share/mise/installs/node/lts/bin/node
    npm: 11.19.0 - /home/carlos/.local/share/mise/installs/node/lts/bin/npm
    pnpm: 12.4.1 - /home/carlos/.local/share/mise/installs/node/lts/bin/pnpm
  Browsers:
    Chrome: 156.0.8078.47
  npmPackages:
    @base-ui/react: ^1.9.0 => 1.9.0 
    @clerk/backend: ~3.26.1 => 3.26.1 
    @clerk/localizations: ~4.24.0 => 4.24.0 
    @clerk/nextjs: ^7.9.19 => 7.9.19 
    @clerk/testing: ^2.2.49 => 2.2.49 
    @clerk/ui: ~1.41.1 => 1.41.1 
    @convex-dev/eslint-plugin: ^5.0.0 => 5.0.0 
    @convex-dev/migrations: ^0.3.7 => 0.3.7 
    @convex-dev/rate-limiter: ^0.4.2 => 0.4.2 
    @convex-dev/resend: ^0.2.10 => 0.2.10 
    @edge-runtime/vm: ^5.0.0 => 5.0.0 
    @nx/js: 23.3.0 => 23.3.0 
    @nx/next: 23.3.0 => 23.3.0 
    @nx/oxlint: 23.3.0 => 23.3.0 
    @nx/playwright: 23.3.0 => 23.3.0 
    @nx/vite: 23.3.0 => 23.3.0 
    @nx/vitest: 23.3.0 => 23.3.0 
    @nx/workspace: 23.3.0 => 23.3.0 
    @playwright/test: ^1.64.1 => 1.64.1 
    @react-pdf/renderer: ^4.9.0 => 4.9.0 
    @swc-node/register: ^1.12.1 => 1.12.1 
    @swc/core: ^1.16.20 => 1.16.20 
    @t3-oss/env-nextjs: ^0.13.11 => 0.13.11 
    @tailwindcss/postcss: ^4.3.3 => 4.3.3 
    @tanstack/react-form: ^1.33.5 => 1.33.5 
    @tanstack/react-query: ^5.104.5 => 5.104.5 
    @tanstack/react-table: ^9.3.1 => 9.3.1 
    @tanstack/store: 0.11.2 => 0.11.2 
    @testing-library/dom: ^10.4.2 => 10.4.2 
    @testing-library/jest-dom: ^7.0.1 => 7.0.1 
    @testing-library/react: ^16.3.3 => 16.3.3 
    @testing-library/user-event: ^14.6.8 => 14.6.8 
    @types/lodash: ^4.17.27 => 4.17.27 
    @types/node: ^26.6.5 => 26.6.5 
    @types/react: 19.3.0 => 19.3.0 
    @types/react-dom: 19.3.0 => 19.3.0 
    @vital-link/ui: workspace:* => 0.1.0 
    @vitejs/plugin-react: ^6.1.2 => 6.1.2 
    @vitest/coverage-v8: ^5.0.5 => 5.0.5 
    @vitest/ui: ^5.0.5 => 5.0.5 
    babel-plugin-react-compiler: ^1.0.0 => 1.0.0 
    class-variance-authority: ^0.7.1 => 0.7.1 
    clsx: ^2.1.1 => 2.1.1 
    cmdk: ^1.1.1 => 1.1.1 
    concurrently: ^10.0.6 => 10.0.6 
    convex: ^1.48.0 => 1.48.0 
    convex-helpers: ^0.1.129 => 0.1.129 
    convex-test: ^0.0.64 => 0.0.64 
    date-fns: ^4.4.0 => 4.4.0 
    date-fns-tz: ^3.2.0 => 3.2.0 
    dotenv: ^18.0.9 => 18.0.9 
    extract-zip: 2.0.1 => 2.0.1 
    fallow: ^3.35.0 => 3.35.0 
    happy-dom: ^20.14.6 => 20.14.6 
    husky: ^9.1.7 => 9.1.7 
    jszip: ^3.10.2 => 3.10.2 
    lint-staged: ^17.6.0 => 17.6.0 
    lodash: ^4.18.1 => 4.18.1 
    lucide-react: ^1.61.0 => 1.61.0 
    next: 16.4.2 => 16.4.2 
    next-intl: ^4.14.9 => 4.14.9 
    next-themes: ^0.4.6 => 0.4.6 
    nuqs: ^2.10.2 => 2.10.2 
    nx: 23.3.0 => 23.3.0 
    oxfmt: ^0.74.0 => 0.74.0 
    oxlint: ^1.89.0 => 1.89.0 
    picocolors: ^1.1.1 => 1.1.1 
    postcss: ^8.5.31 => 8.5.31 
    qrcode.react: ^4.2.0 => 4.2.0 
    react: 19.3.1 => 19.3.1 
    react-day-picker: ^10.0.2 => 10.0.2 
    react-dom: 19.3.1 => 19.3.1 
    react-dropzone: ^20.1.4 => 20.1.4 
    react-email: ^6.11.3 => 6.11.3 
    react-rnd: ^10.5.3 => 10.5.3 
    react-use: ^17.6.1 => 17.6.1 
    resend: ^6.34.1 => 6.34.1 
    shadcn: ^4.22.1 => 4.22.1 
    sonner: ^2.0.8 => 2.0.8 
    svix: ~2.8.0 => 2.8.0 
    tailwind-merge: ^3.7.0 => 3.7.0 
    tailwindcss: ^4.3.3 => 4.3.3 
    tsx: ^4.23.15 => 4.23.15 
    tw-animate-css: ^1.4.0 => 1.4.0 
    typescript: ^6.0.3 => 6.0.3 
    vite: ^8.3.5 => 8.3.5 
    vitest: ^5.0.5 => 5.0.5 
    xlsx: ^0.18.5 => 0.18.5 
    zod: ^4.6.7 => 4.6.7 
    zustand: ^5.0.16 => 5.0.16 


Done in 944ms using pnpm v12.4.1

Activity

  1. luchillo17 commented on Oct 24, 2026

    @luchillo17
    Author

    Forgot to attach the React Devtools profiling file:

    profiling-data.10-23-2026.00-56-32.json

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageA ticket that needs to be triaged by a team member

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions