[GH-ISSUE #8194] EXPO - NextJs - Better-auth : TypeError: Cannot read private member #state #19653

Closed
opened 2026-04-15 18:56:15 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @C0wnuts on GitHub (Feb 27, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8194

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

1 : Create a NextJs API Backend with Better-auth / Prisma (pg plugins) / Prisma / better-auth/expo (last current versions for everything)
2 : Setup Better-auth configuration and route :

api/auth/[...all]/route.ts

import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);

3 : Build the NextJs Backend in standalone mode : npm run build :

next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
};

export default nextConfig;

4 : run it : node .next/standalone/server.js
5 : Setup React Native / Expo authentication request / session check with default auth-client from documentation :

auth-client.ts

import { Urls } from "@/constants/Urls";
import { expoClient } from "@better-auth/expo/client";
import { createAuthClient } from "better-auth/react";
import * as SecureStore from "expo-secure-store";

export const authClient = createAuthClient({
    baseURL: 'http://192.168.0.114:3000',
    plugins: [
        expoClient({
            scheme: "my-app",
            storagePrefix: "my-app",
            storage: SecureStore,
        })
    ]
});

6 : Try to authenticate (emailPassword and Google are both impacted, but I think all kind of authentication methods are impacted) :

const { error: signInError } = await authClient.signIn.email({ email, password });

7 : Look at the Backend NextJs logs each time you try to authenticate :

TypeError: Cannot read private member #state from an object whose class did not declare it
    at Object.onRequest (.next/server/chunks/[root-of-the-server]__9941c441._.js:398:13686)
    at Object.onRequest (.next/server/chunks/[root-of-the-server]__9941c441._.js:395:3949)
    at handler (.next/server/chunks/[root-of-the-server]__9941c441._.js:395:3387)
    at <unknown> (.next/server/chunks/[root-of-the-server]__9941c441._.js:398:6519)
    at <unknown> (.next/server/chunks/[root-of-the-server]__9941c441._.js:1:191984)

This error lead to error 500 on mobile side.


But, If I use the following api/auth/[...all]/route.ts , everything is working well (authentication, user session check, etc.) :

import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
import { type NextRequest } from "next/server";

const authHandler = toNextJsHandler(auth);

function asPlainRequest(req: NextRequest): Request {
    const hasBody = req.method !== 'GET' && req.method !== 'HEAD';
    return new Request(req.url, {
        method: req.method,
        headers: Object.fromEntries(req.headers.entries()),
        ...(hasBody && {
            body: req.body,
            duplex: 'half',
        }),
    });
}

export function GET(req: NextRequest) {
    return authHandler.GET(asPlainRequest(req));
}

export function POST(req: NextRequest) {
    return authHandler.POST(asPlainRequest(req));
}

Current vs. Expected behavior

By following the documentation I have to be able to use better-auth to authenticate my mobile users. But I don't know what am I doing wrong, or if there is a bug but with Built version of NextJs backend, I face Cannot read private member #state from an object whose class did not declare it errors.

What version of Better Auth are you using?

1.4.19

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:49:24 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T8132",
    "release": "25.3.0",
    "cpuCount": 10,
    "cpuModel": "Apple M4",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.58 GB"
  },
  "node": {
    "version": "v24.13.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.10.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.1.6"
    },
    {
      "name": "react",
      "version": "19.2.4"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "8.19.0"
    },
    {
      "name": "@prisma/client",
      "version": "7.4.1"
    }
  ],
  "betterAuth": {
    "version": "1.4.19",
    "config": {
      "emailAndPassword": {
        "enabled": true
      },
      "baseURL": "http://localhost:3000",
      "socialProviders": {
        "google": {
          "prompt": "select_account",
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "plugins": [
        {
          "name": "expo",
          "config": {
            "id": "expo",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {}
          }
        }
      ],
      "trustedOrigins": [
        "my-app://",
        "https://mydomain.fr",
        "https://www.mydomain.fr"
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { db } from "@/lib/prismadb";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { expo } from "@better-auth/expo";

export const auth = betterAuth({
    database: prismaAdapter(db, { provider: 'postgresql' }),
    emailAndPassword: {
        enabled: true,
    },
    baseURL: process.env.BETTER_AUTH_URL,
    socialProviders: {
        google: {
            prompt: "select_account",
            clientId: process.env.GOOGLE_CLIENT_ID as string,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
        },
    },
    plugins: [expo()],
    trustedOrigins: [
        "my-app://",
        "https://mydomain.fr",
        "https://www.mydomain.fr",
    ].filter(Boolean),
});

Additional context

I face this errors on Macos and Docker environment. I never faced it in dev, only in production one with built NextJs backend.

Originally created by @C0wnuts on GitHub (Feb 27, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8194 Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1 : Create a NextJs API Backend with Better-auth / Prisma (pg plugins) / Prisma / better-auth/expo (last current versions for everything) 2 : Setup Better-auth configuration and route : _api/auth/[...all]/route.ts_ ``` import { auth } from "@/lib/auth"; import { toNextJsHandler } from "better-auth/next-js"; export const { GET, POST } = toNextJsHandler(auth); ``` 3 : Build the NextJs Backend in standalone mode : `npm run build` : _next.config.ts_ ``` import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: 'standalone', poweredByHeader: false, }; export default nextConfig; ``` 4 : run it : `node .next/standalone/server.js` 5 : Setup React Native / Expo authentication request / session check with default auth-client from documentation : _auth-client.ts_ ``` import { Urls } from "@/constants/Urls"; import { expoClient } from "@better-auth/expo/client"; import { createAuthClient } from "better-auth/react"; import * as SecureStore from "expo-secure-store"; export const authClient = createAuthClient({ baseURL: 'http://192.168.0.114:3000', plugins: [ expoClient({ scheme: "my-app", storagePrefix: "my-app", storage: SecureStore, }) ] }); ``` 6 : Try to authenticate (emailPassword and Google are both impacted, but I think all kind of authentication methods are impacted) : `const { error: signInError } = await authClient.signIn.email({ email, password });` 7 : Look at the Backend NextJs logs each time you try to authenticate : ``` TypeError: Cannot read private member #state from an object whose class did not declare it at Object.onRequest (.next/server/chunks/[root-of-the-server]__9941c441._.js:398:13686) at Object.onRequest (.next/server/chunks/[root-of-the-server]__9941c441._.js:395:3949) at handler (.next/server/chunks/[root-of-the-server]__9941c441._.js:395:3387) at <unknown> (.next/server/chunks/[root-of-the-server]__9941c441._.js:398:6519) at <unknown> (.next/server/chunks/[root-of-the-server]__9941c441._.js:1:191984) ``` This error lead to error 500 on mobile side. --- But, If I use the following _api/auth/[...all]/route.ts_ , everything is working well (authentication, user session check, etc.) : ``` import { auth } from "@/lib/auth"; import { toNextJsHandler } from "better-auth/next-js"; import { type NextRequest } from "next/server"; const authHandler = toNextJsHandler(auth); function asPlainRequest(req: NextRequest): Request { const hasBody = req.method !== 'GET' && req.method !== 'HEAD'; return new Request(req.url, { method: req.method, headers: Object.fromEntries(req.headers.entries()), ...(hasBody && { body: req.body, duplex: 'half', }), }); } export function GET(req: NextRequest) { return authHandler.GET(asPlainRequest(req)); } export function POST(req: NextRequest) { return authHandler.POST(asPlainRequest(req)); } ``` ### Current vs. Expected behavior By following the documentation I have to be able to use better-auth to authenticate my mobile users. But I don't know what am I doing wrong, or if there is a bug but with Built version of NextJs backend, I face `Cannot read private member #state from an object whose class did not declare it` errors. ### What version of Better Auth are you using? 1.4.19 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:49:24 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T8132", "release": "25.3.0", "cpuCount": 10, "cpuModel": "Apple M4", "totalMemory": "32.00 GB", "freeMemory": "0.58 GB" }, "node": { "version": "v24.13.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.10.0" }, "frameworks": [ { "name": "next", "version": "16.1.6" }, { "name": "react", "version": "19.2.4" } ], "databases": [ { "name": "pg", "version": "8.19.0" }, { "name": "@prisma/client", "version": "7.4.1" } ], "betterAuth": { "version": "1.4.19", "config": { "emailAndPassword": { "enabled": true }, "baseURL": "http://localhost:3000", "socialProviders": { "google": { "prompt": "select_account", "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "plugins": [ { "name": "expo", "config": { "id": "expo", "hooks": { "after": [ {} ] }, "endpoints": {} } } ], "trustedOrigins": [ "my-app://", "https://mydomain.fr", "https://www.mydomain.fr" ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { db } from "@/lib/prismadb"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { expo } from "@better-auth/expo"; export const auth = betterAuth({ database: prismaAdapter(db, { provider: 'postgresql' }), emailAndPassword: { enabled: true, }, baseURL: process.env.BETTER_AUTH_URL, socialProviders: { google: { prompt: "select_account", clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, }, plugins: [expo()], trustedOrigins: [ "my-app://", "https://mydomain.fr", "https://www.mydomain.fr", ].filter(Boolean), }); ``` ### Additional context I face this errors on Macos and Docker environment. I never faced it in dev, only in production one with built NextJs backend.
GiteaMirror added the lockedbug labels 2026-04-15 18:56:15 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Feb 27, 2026):

Hi @C0wnuts! I'm Dosu and I’m helping the better-auth team.

This looks like a dual module hazard issue that's known to affect Better Auth in production/standalone builds. The #state error you're seeing is likely from minified code where the private field name appears as #state, and it occurs when better-auth and better-call (its internal router) are resolved to different module instances by the bundler.

Recommended fix: Add better-auth to your Next.js server external packages in next.config.ts:

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
  serverExternalPackages: ['better-auth'],
};

This prevents Next.js from bundling better-auth and avoids the module resolution conflict.

Your workaround of converting NextRequest to a plain Request object is a known pattern for Expo plugin compatibility, and it works because it sidesteps the type checking that triggers the issue.

For more details on this class of issues, see the Better Auth FAQ on dual module hazard.

If serverExternalPackages doesn't resolve it, could you share which package manager you're using? Yarn v1 in particular has been reported to cause similar issues with module resolution.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3975100629 --> @dosubot[bot] commented on GitHub (Feb 27, 2026): <!-- Greeting --> Hi @C0wnuts! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This looks like a **dual module hazard issue** that's known to affect Better Auth in production/standalone builds. The `#state` error you're seeing is likely from minified code where the private field name appears as `#state`, and it occurs when `better-auth` and `better-call` (its internal router) are resolved to different module instances by the bundler. **Recommended fix:** Add `better-auth` to your Next.js server external packages in `next.config.ts`: ```typescript const nextConfig: NextConfig = { output: 'standalone', poweredByHeader: false, serverExternalPackages: ['better-auth'], }; ``` This prevents Next.js from bundling better-auth and avoids the module resolution conflict. Your workaround of converting `NextRequest` to a plain `Request` object is a [known pattern for Expo plugin compatibility](https://github.com/better-auth/better-auth/pull/6420), and it works because it sidesteps the type checking that triggers the issue. For more details on this class of issues, see the [Better Auth FAQ on dual module hazard](https://www.better-auth.com/docs/reference/faq#dual-module-hazard-issue). If `serverExternalPackages` doesn't resolve it, could you share which package manager you're using? Yarn v1 in particular has been reported to cause similar issues with module resolution. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/3da85297-1615-4550-8ca3-daa9933351f7?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20looks%20like%20a%20%2A%2Adual%20module%20hazard%20issue%2A%2A%20that%27s%20known%20to%20affect%20Better%20Auth%20in%20production/standalone%20builds.%20The%20%60%23state%60%20error%20you%27re%20seeing%20is%20likely%20from%20minified%20code%20where%20the%20private%20field%20name%20appears%20as%20%60%23state%60%2C%20and%20it%20occurs%20when%20%60better-auth%60%20and%20%60better-call%60%20%28its%20internal%20router%29%20are%20resolved%20to%20different%20module%20instances%20by%20the%20bundler.%0A%0A%2A%2ARecommended%20fix%3A%2A%2A%20Add%20%60better-auth%60%20to%20your%20Next.js%20server%20external%20packages%20in%20%60next.config.ts%60%3A%0A%0A%60%60%60typescript%0Aconst%20nextConfig%3A%20NextConfig%20%3D%20%7B%0A%20%20output%3A%20%27standalone%27%2C%0A%20%20poweredByHeader%3A%20false%2C%0A%20%20serverExternalPackages%3A%20%5B%27better-auth%27%5D%2C%0A%7D%3B%0A%60%60%60%0A%0AThis%20prevents%20Next.js%20from%20bundling%20better-auth%20and%20avoids%20the%20module%20resolution%20conflict.%0A%0AYour%20workaround%20of%20converting%20%60NextRequest%60%20to%20a%20plain%20%60Request%60%20object%20is%20a%20%5Bknown%20pattern%20for%20Expo%20plugin%20compatibility%5D%28https%3A//github.com/better-auth/better-auth/pull/6420%29%2C%20and%20it%20works%20because%20it%20sidesteps%20the%20type%20checking%20that%20triggers%20the%20issue.%0A%0AFor%20more%20details%20on%20this%20class%20of%20issues%2C%20see%20the%20%5BBetter%20Auth%20FAQ%20on%20dual%20module%20hazard%5D%28https%3A//www.better-auth.com/docs/reference/faq%23dual-module-hazard-issue%29.%0A%0AIf%20%60serverExternalPackages%60%20doesn%27t%20resolve%20it%2C%20could%20you%20share%20which%20package%20manager%20you%27re%20using%3F%20Yarn%20v1%20in%20particular%20has%20been%20reported%20to%20cause%20similar%20issues%20with%20module%20resolution.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/8194)
Author
Owner

@C0wnuts commented on GitHub (Feb 27, 2026):

@dosu the provided patch seems to change the errors.
Now for session check I have :

⨯ TypeError: Cannot read private member #state from an object whose class did not declare it
    at Object.onRequest (.next/server/chunks/[root-of-the-server]__ee2cd815._.js:37:48821)
    at async u (.next/server/chunks/[root-of-the-server]__6e259c41._.js:1:4394)

And for authentication requests :

⨯ TypeError: Cannot read private member #state from an object whose class did not declare it
    at Object.onRequest (.next/server/chunks/[root-of-the-server]__ee2cd815._.js:37:48821)
<!-- gh-comment-id:3975169502 --> @C0wnuts commented on GitHub (Feb 27, 2026): @dosu the provided patch seems to change the errors. Now for session check I have : ``` ⨯ TypeError: Cannot read private member #state from an object whose class did not declare it at Object.onRequest (.next/server/chunks/[root-of-the-server]__ee2cd815._.js:37:48821) at async u (.next/server/chunks/[root-of-the-server]__6e259c41._.js:1:4394) ``` And for authentication requests : ``` ⨯ TypeError: Cannot read private member #state from an object whose class did not declare it at Object.onRequest (.next/server/chunks/[root-of-the-server]__ee2cd815._.js:37:48821) ```
Author
Owner

@dosubot[bot] commented on GitHub (Feb 27, 2026):

The error persisting after adding serverExternalPackages suggests the dual module hazard may still be occurring. A few things to try:

1. Also add better-call to externals:

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
  serverExternalPackages: ['better-auth', 'better-call'],
};

2. Force single better-call instance via package.json:

If you're using npm, try explicitly installing better-call and adding a resolution:

{
  "dependencies": {
    "better-call": "^1.1.8"
  },
  "overrides": {
    "better-call": "^1.1.8"
  }
}

For yarn, use resolutions instead of overrides. For pnpm, use pnpm.overrides.

3. Diagnose the issue:

Run this to check for duplicate instances:

npm ls better-call
# or: pnpm why better-call / yarn why better-call

4. If using Turbopack: Try building without it (next build instead of next build --turbo) as there are known Turbopack compatibility issues with Better Auth in Next.js 16.

Which package manager are you using? That might help narrow down the specific fix needed.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3975176288 --> @dosubot[bot] commented on GitHub (Feb 27, 2026): <!-- Answer --> The error persisting after adding `serverExternalPackages` suggests the dual module hazard may still be occurring. A few things to try: **1. Also add `better-call` to externals:** ```typescript const nextConfig: NextConfig = { output: 'standalone', poweredByHeader: false, serverExternalPackages: ['better-auth', 'better-call'], }; ``` **2. Force single `better-call` instance via package.json:** If you're using npm, try explicitly installing `better-call` and adding a resolution: ```json { "dependencies": { "better-call": "^1.1.8" }, "overrides": { "better-call": "^1.1.8" } } ``` For yarn, use `resolutions` instead of `overrides`. For pnpm, use `pnpm.overrides`. **3. Diagnose the issue:** Run this to check for duplicate instances: ```bash npm ls better-call # or: pnpm why better-call / yarn why better-call ``` **4. If using Turbopack:** Try building without it (`next build` instead of `next build --turbo`) as there are [known Turbopack compatibility issues](https://github.com/better-auth/better-auth/issues/7023) with Better Auth in Next.js 16. Which package manager are you using? That might help narrow down the specific fix needed. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/55bb4112-8802-4140-a686-910fe8c7653f?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20error%20persisting%20after%20adding%20%60serverExternalPackages%60%20suggests%20the%20dual%20module%20hazard%20may%20still%20be%20occurring.%20A%20few%20things%20to%20try%3A%0A%0A%2A%2A1.%20Also%20add%20%60better-call%60%20to%20externals%3A%2A%2A%0A%60%60%60typescript%0Aconst%20nextConfig%3A%20NextConfig%20%3D%20%7B%0A%20%20output%3A%20%27standalone%27%2C%0A%20%20poweredByHeader%3A%20false%2C%0A%20%20serverExternalPackages%3A%20%5B%27better-auth%27%2C%20%27better-call%27%5D%2C%0A%7D%3B%0A%60%60%60%0A%0A%2A%2A2.%20Force%20single%20%60better-call%60%20instance%20via%20package.json%3A%2A%2A%0A%0AIf%20you%27re%20using%20npm%2C%20try%20explicitly%20installing%20%60better-call%60%20and%20adding%20a%20resolution%3A%0A%60%60%60json%0A%7B%0A%20%20%22dependencies%22%3A%20%7B%0A%20%20%20%20%22better-call%22%3A%20%22%5E1.1.8%22%0A%20%20%7D%2C%0A%20%20%22overrides%22%3A%20%7B%0A%20%20%20%20%22better-call%22%3A%20%22%5E1.1.8%22%0A%20%20%7D%0A%7D%0A%60%60%60%0A%0AFor%20yarn%2C%20use%20%60resolutions%60%20instead%20of%20%60overrides%60.%20For%20pnpm%2C%20use%20%60pnpm.overrides%60.%0A%0A%2A%2A3.%20Diagnose%20the%20issue%3A%2A%2A%0A%0ARun%20this%20to%20check%20for%20duplicate%20instances%3A%0A%60%60%60bash%0Anpm%20ls%20better-call%0A%23%20or%3A%20pnpm%20why%20better-call%20/%20yarn%20why%20better-call%0A%60%60%60%0A%0A%2A%2A4.%20If%20using%20Turbopack%3A%2A%2A%20Try%20building%20without%20it%20%28%60next%20build%60%20instead%20of%20%60next%20build%20--turbo%60%29%20as%20there%20are%20%5Bknown%20Turbopack%20compatibility%20issues%5D%28https%3A//github.com/better-auth/better-auth/issues/7023%29%20with%20Better%20Auth%20in%20Next.js%2016.%0A%0AWhich%20package%20manager%20are%20you%20using%3F%20That%20might%20help%20narrow%20down%20the%20specific%20fix%20needed.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/8194)
Author
Owner

@C0wnuts commented on GitHub (Feb 27, 2026):

@dosu I'm using npm as package manager.
I followed your proposal by adding better-call to package.json and serverExternalPackages in next.config.ts :

{
  "dependencies": {
    "better-call": "^1.1.8"
  },
  "overrides": {
    "better-call": "^1.1.8"
  }
}

Also npm ls better-call give me :

myapp@0.1.0 /path/to/app/myapp/src
├─┬ @better-auth/expo@1.4.19
│ ├─┬ @better-auth/core@1.4.19
│ │ └── better-call@1.3.3 deduped
│ └── better-call@1.3.3 deduped
├─┬ better-auth@1.4.19
│ └── better-call@1.3.3 deduped
└── better-call@1.3.3 overridden

Nothing changed, I still face errors :

TypeError: Cannot read private member #state from an object whose class did not declare it
    at Object.onRequest (.next/server/chunks/[root-of-the-server]__705e7f2d._.js:37:37456)
    at async u (.next/server/chunks/[root-of-the-server]__8b080d59._.js:1:4394)
⨯ TypeError: Cannot read private member #state from an object whose class did not declare it
    at Object.onRequest (.next/server/chunks/[root-of-the-server]__705e7f2d._.js:37:37456)
<!-- gh-comment-id:3975216775 --> @C0wnuts commented on GitHub (Feb 27, 2026): @dosu I'm using npm as package manager. I followed your proposal by adding better-call to package.json and serverExternalPackages in next.config.ts : ``` { "dependencies": { "better-call": "^1.1.8" }, "overrides": { "better-call": "^1.1.8" } } ``` Also `npm ls better-call` give me : ``` myapp@0.1.0 /path/to/app/myapp/src ├─┬ @better-auth/expo@1.4.19 │ ├─┬ @better-auth/core@1.4.19 │ │ └── better-call@1.3.3 deduped │ └── better-call@1.3.3 deduped ├─┬ better-auth@1.4.19 │ └── better-call@1.3.3 deduped └── better-call@1.3.3 overridden ``` Nothing changed, I still face errors : ``` TypeError: Cannot read private member #state from an object whose class did not declare it at Object.onRequest (.next/server/chunks/[root-of-the-server]__705e7f2d._.js:37:37456) at async u (.next/server/chunks/[root-of-the-server]__8b080d59._.js:1:4394) ⨯ TypeError: Cannot read private member #state from an object whose class did not declare it at Object.onRequest (.next/server/chunks/[root-of-the-server]__705e7f2d._.js:37:37456) ```
Author
Owner

@dosubot[bot] commented on GitHub (Feb 27, 2026):

The npm ls better-call output shows the override is working correctly (all instances deduped to 1.3.3), so the issue isn't package manager resolution anymore. Since you're on Next.js 16.1.6, this is likely a Turbopack compatibility issue where serverExternalPackages is being ignored during the build.

Try these in order:

  1. Build without Turbopack:
# Use standard webpack instead of Turbopack
npx next build

(Make sure you're not passing --turbo or --turbopack flags)

  1. If that doesn't help, try downgrading Next.js temporarily:
npm install next@15.3.3
npm run build

Next.js 16+ with Turbopack has known issues where it ignores serverExternalPackages and bundles better-auth anyway, which causes the #state error when the bundled code doesn't properly preserve the class private fields.

Your original workaround (converting NextRequest to plain Request) works because it avoids the code path where the private field access fails. That's a valid production workaround if the build configuration changes don't resolve it.

Are you explicitly using Turbopack for your builds?

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3975225326 --> @dosubot[bot] commented on GitHub (Feb 27, 2026): <!-- Answer --> The `npm ls better-call` output shows the override is working correctly (all instances deduped to 1.3.3), so the issue isn't package manager resolution anymore. Since you're on Next.js 16.1.6, this is likely a [Turbopack compatibility issue](https://github.com/better-auth/better-auth/issues/7435) where `serverExternalPackages` is being ignored during the build. **Try these in order:** 1. **Build without Turbopack:** ```bash # Use standard webpack instead of Turbopack npx next build ``` (Make sure you're not passing `--turbo` or `--turbopack` flags) 2. **If that doesn't help, try downgrading Next.js temporarily:** ```bash npm install next@15.3.3 npm run build ``` Next.js 16+ with Turbopack has [known issues](https://github.com/better-auth/better-auth/issues/6781#issuecomment-3657135145) where it ignores `serverExternalPackages` and bundles better-auth anyway, which causes the `#state` error when the bundled code doesn't properly preserve the class private fields. Your original workaround (converting `NextRequest` to plain `Request`) works because it avoids the code path where the private field access fails. That's a valid production workaround if the build configuration changes don't resolve it. Are you explicitly using Turbopack for your builds? <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9c737457-7889-4e81-93c8-101f06436ff5?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20%60npm%20ls%20better-call%60%20output%20shows%20the%20override%20is%20working%20correctly%20%28all%20instances%20deduped%20to%201.3.3%29%2C%20so%20the%20issue%20isn%27t%20package%20manager%20resolution%20anymore.%20Since%20you%27re%20on%20Next.js%2016.1.6%2C%20this%20is%20likely%20a%20%5BTurbopack%20compatibility%20issue%5D%28https%3A//github.com/better-auth/better-auth/issues/7435%29%20where%20%60serverExternalPackages%60%20is%20being%20ignored%20during%20the%20build.%0A%0A%2A%2ATry%20these%20in%20order%3A%2A%2A%0A%0A1.%20%2A%2ABuild%20without%20Turbopack%3A%2A%2A%0A%60%60%60bash%0A%23%20Use%20standard%20webpack%20instead%20of%20Turbopack%0Anpx%20next%20build%0A%60%60%60%0A%28Make%20sure%20you%27re%20not%20passing%20%60--turbo%60%20or%20%60--turbopack%60%20flags%29%0A%0A2.%20%2A%2AIf%20that%20doesn%27t%20help%2C%20try%20downgrading%20Next.js%20temporarily%3A%2A%2A%0A%60%60%60bash%0Anpm%20install%20next%4015.3.3%0Anpm%20run%20build%0A%60%60%60%0A%0ANext.js%2016%2B%20with%20Turbopack%20has%20%5Bknown%20issues%5D%28https%3A//github.com/better-auth/better-auth/issues/6781%23issuecomment-3657135145%29%20where%20it%20ignores%20%60serverExternalPackages%60%20and%20bundles%20better-auth%20anyway%2C%20which%20causes%20the%20%60%23state%60%20error%20when%20the%20bundled%20code%20doesn%27t%20properly%20preserve%20the%20class%20private%20fields.%0A%0AYour%20original%20workaround%20%28converting%20%60NextRequest%60%20to%20plain%20%60Request%60%29%20works%20because%20it%20avoids%20the%20code%20path%20where%20the%20private%20field%20access%20fails.%20That%27s%20a%20valid%20production%20workaround%20if%20the%20build%20configuration%20changes%20don%27t%20resolve%20it.%0A%0AAre%20you%20explicitly%20using%20Turbopack%20for%20your%20builds%3F)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/8194)
Author
Owner

@xXMrNidaXx commented on GitHub (Feb 27, 2026):

Root Cause: Private Field Mangling in Standalone Build

The error Cannot read private member #state from an object whose class did not declare it is a classic symptom of how Next.js standalone mode bundles/minifies code.

Why it happens

When Next.js builds in standalone mode:

  1. Code gets bundled with SWC/Turbopack and potentially tree-shaken
  2. Private class fields (#state) require special handling — the class instance and the field access must reference the same class definition
  3. If better-auth's internal code gets split across chunks or the class is duplicated during bundling, the private field check fails

Your workaround works because asPlainRequest() creates a fresh Request object that doesn't carry any internal state from Next.js's wrapped request classes.

The actual problem

The issue is likely in how toNextJsHandler interacts with Next.js's internal NextRequest class:

// NextRequest likely has private fields like #state
// When bundled, class identity breaks:
req instanceof NextRequest  // might be false
req.#state                  // TypeError - different class definition

Recommendation for maintainers

The fix should be in toNextJsHandler — convert NextRequest to a plain Request internally before processing:

export function toNextJsHandler(auth: Auth) {
  return {
    GET: async (req: NextRequest) => {
      const plainReq = toPlainRequest(req)  // Convert early
      return auth.handler(plainReq)
    },
    POST: async (req: NextRequest) => {
      const plainReq = toPlainRequest(req)
      return auth.handler(plainReq)
    }
  }
}

function toPlainRequest(req: NextRequest): Request {
  const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
  return new Request(req.url, {
    method: req.method,
    headers: Object.fromEntries(req.headers.entries()),
    ...(hasBody && { body: req.body, duplex: 'half' }),
  })
}

This would make the standalone build work without users needing the workaround.

For @StephaneBrault (OP)

Your workaround is correct and production-safe. The duplex: 'half' is essential for streaming bodies. You might also want to preserve more headers for completeness:

headers: new Headers(req.headers),  // Preserves multi-value headers
<!-- gh-comment-id:3975332346 --> @xXMrNidaXx commented on GitHub (Feb 27, 2026): ## Root Cause: Private Field Mangling in Standalone Build The error `Cannot read private member #state from an object whose class did not declare it` is a classic symptom of how Next.js standalone mode bundles/minifies code. ### Why it happens When Next.js builds in `standalone` mode: 1. Code gets bundled with SWC/Turbopack and potentially tree-shaken 2. Private class fields (`#state`) require special handling — the class instance and the field access must reference the *same* class definition 3. If better-auth's internal code gets split across chunks or the class is duplicated during bundling, the private field check fails Your workaround works because `asPlainRequest()` creates a fresh `Request` object that doesn't carry any internal state from Next.js's wrapped request classes. ### The actual problem The issue is likely in how `toNextJsHandler` interacts with Next.js's internal `NextRequest` class: ```typescript // NextRequest likely has private fields like #state // When bundled, class identity breaks: req instanceof NextRequest // might be false req.#state // TypeError - different class definition ``` ### Recommendation for maintainers The fix should be in `toNextJsHandler` — convert `NextRequest` to a plain `Request` internally before processing: ```typescript export function toNextJsHandler(auth: Auth) { return { GET: async (req: NextRequest) => { const plainReq = toPlainRequest(req) // Convert early return auth.handler(plainReq) }, POST: async (req: NextRequest) => { const plainReq = toPlainRequest(req) return auth.handler(plainReq) } } } function toPlainRequest(req: NextRequest): Request { const hasBody = req.method !== 'GET' && req.method !== 'HEAD' return new Request(req.url, { method: req.method, headers: Object.fromEntries(req.headers.entries()), ...(hasBody && { body: req.body, duplex: 'half' }), }) } ``` This would make the standalone build work without users needing the workaround. ### For @StephaneBrault (OP) Your workaround is correct and production-safe. The `duplex: 'half'` is essential for streaming bodies. You might also want to preserve more headers for completeness: ```typescript headers: new Headers(req.headers), // Preserves multi-value headers ```
Author
Owner

@C0wnuts commented on GitHub (Feb 27, 2026):

Thanks for your response and the additional details; I have a better understanding of the issue now.

I definitely prefer your version of the workaround over the various proposals suggested by dosubot.

<!-- gh-comment-id:3975570862 --> @C0wnuts commented on GitHub (Feb 27, 2026): Thanks for your response and the additional details; I have a better understanding of the issue now. I definitely prefer your version of the workaround over the various proposals suggested by dosubot.
Author
Owner

@himself65 commented on GitHub (Feb 28, 2026):

This is a Next.js/Turbopack bundling issue, not a better-auth bug.

The #state private field is from Node.js's native Request class (undici). In standalone builds, Turbopack can duplicate module scopes across chunks, so the Request instance passed to the route handler comes from a different copy of the class than the code accessing .url/.method/.headers — and JavaScript private fields are bound to the exact class definition that declared them.

This would affect any library receiving a Request object in Next.js standalone mode, not just better-auth. The fix should come from Next.js/Turbopack.

Workaround: Convert NextRequest to a plain Request in your route handler, as you already discovered:

function asPlainRequest(req: NextRequest): Request {
  const hasBody = req.method !== 'GET' && req.method !== 'HEAD';
  return new Request(req.url, {
    method: req.method,
    headers: new Headers(req.headers),
    ...(hasBody && { body: req.body, duplex: 'half' }),
  });
}
<!-- gh-comment-id:3976419614 --> @himself65 commented on GitHub (Feb 28, 2026): This is a Next.js/Turbopack bundling issue, not a better-auth bug. The `#state` private field is from **Node.js's native `Request` class** (undici). In standalone builds, Turbopack can duplicate module scopes across chunks, so the `Request` instance passed to the route handler comes from a different copy of the class than the code accessing `.url`/`.method`/`.headers` — and JavaScript private fields are bound to the exact class definition that declared them. This would affect any library receiving a `Request` object in Next.js standalone mode, not just better-auth. The fix should come from Next.js/Turbopack. **Workaround:** Convert `NextRequest` to a plain `Request` in your route handler, as you already discovered: ```ts function asPlainRequest(req: NextRequest): Request { const hasBody = req.method !== 'GET' && req.method !== 'HEAD'; return new Request(req.url, { method: req.method, headers: new Headers(req.headers), ...(hasBody && { body: req.body, duplex: 'half' }), }); } ```
Author
Owner

@himself65 commented on GitHub (Feb 28, 2026):

Reopening to correct my previous analysis.

After further investigation, the root cause is Node.js 24.x + undici, not Turbopack.

Your system info shows "version": "v24.13.0". Node.js 24.x ships with a newer version of undici where Request/Response use private fields (#state). Next.js wraps Request objects in a Proxy for internal purposes, and JavaScript Proxy objects cannot forward private field access — this is a fundamental language limitation.

When any code accesses .url, .method, .headers etc. on the proxied Request, it internally tries this.#state, but this is the Proxy, not the actual Request instance, causing the error.

This is tracked upstream: https://github.com/nodejs/undici/issues/4290

Workaround options:

  1. Use Node.js 22.x LTS instead of 24.x
  2. Convert to plain Request in your route handler (as you already discovered)

Closing again as this is not a better-auth issue — it's a known Node.js 24.x / undici / Proxy incompatibility.

<!-- gh-comment-id:3976438870 --> @himself65 commented on GitHub (Feb 28, 2026): Reopening to correct my previous analysis. After further investigation, the root cause is **Node.js 24.x + undici**, not Turbopack. Your system info shows `"version": "v24.13.0"`. Node.js 24.x ships with a newer version of undici where `Request`/`Response` use private fields (`#state`). Next.js wraps `Request` objects in a `Proxy` for internal purposes, and **JavaScript Proxy objects cannot forward private field access** — this is a fundamental language limitation. When any code accesses `.url`, `.method`, `.headers` etc. on the proxied `Request`, it internally tries `this.#state`, but `this` is the Proxy, not the actual `Request` instance, causing the error. This is tracked upstream: https://github.com/nodejs/undici/issues/4290 **Workaround options:** 1. **Use Node.js 22.x LTS** instead of 24.x 2. **Convert to plain `Request`** in your route handler (as you already discovered) Closing again as this is not a better-auth issue — it's a known Node.js 24.x / undici / Proxy incompatibility.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19653