[GH-ISSUE #5458] Webpack fails to resolve React hooks from better-auth/react with Next.js 15 and React 19 #10252

Closed
opened 2026-04-13 06:14:59 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @lohrm-stabl on GitHub (Oct 21, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5458

Bug Report: Webpack fails to resolve React hooks from better-auth/react with Next.js 15 and React 19

Description

When building a Next.js 15.4.7 application with React 19.1.0, webpack fails to resolve React hooks (useRef, useSyncExternalStore) from the better-auth/react module, causing the build to fail with import errors.

Error Message

Failed to compile.

./node_modules/better-auth/dist/client/react/index.mjs
Attempted import error: 'useRef' is not exported from 'react' (imported as 'useRef').

Import trace for requested module:
./node_modules/better-auth/dist/client/react/index.mjs
./src/lib/auth/config/client.ts

./node_modules/better-auth/dist/client/react/index.mjs
Attempted import error: 'useSyncExternalStore' is not exported from 'react' (imported as 'useSyncExternalStore').

Import trace for requested module:
./node_modules/better-auth/dist/client/react/index.mjs
./src/lib/auth/config/client.ts

> Build failed because of webpack errors

Environment

  • better-auth version: 1.3.27 and 1.3.28 (issue occurs on both versions)
  • Next.js version: 15.4.7
  • React version: 19.1.0
  • React DOM version: 19.1.0
  • Node.js version: v22.19.0
  • Package manager: Bun 1.3.0
  • TypeScript version: 5.9.3
  • Build command: next build

Reproduction Steps

  1. Create a Next.js 15.4.7 project with React 19.1.0
  2. Install better-auth@1.3.28 or better-auth@1.3.27
  3. Import and use createAuthClient from better-auth/react:
// src/lib/auth/config/client.ts
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  baseURL: "http://localhost:3000",
});
  1. Run next build
  2. Build fails with the error above

Root Cause

Webpack resolves better-auth/react to the ESM module (index.mjs), which uses named imports from React:

// node_modules/better-auth/dist/client/react/index.mjs
import { useRef, useCallback, useSyncExternalStore } from 'react';

However, webpack's module resolution for the .mjs file fails to properly resolve these React exports, even though they are valid and exist in React 19.1.0.

Observed Behavior

  • The CommonJS version (index.cjs) works correctly and uses react.useRef and react.useSyncExternalStore
  • The ESM version (index.mjs) fails during webpack compilation
  • Both files are properly exported in package.json exports field

Workaround

Adding a webpack alias in next.config.js to force the use of the CommonJS version resolves the issue:

// next.config.js
const { version } = require("./package.json");
const path = require("path");

/** @type {import('next').NextConfig} */
const nextConfig = {
  publicRuntimeConfig: {
    version,
  },
  transpilePackages: ["better-auth"],
  webpack: (config) => {
    // Alias better-auth/react to use CommonJS version to fix React 19 compatibility
    config.resolve.alias = {
      ...config.resolve.alias,
      'better-auth/react$': path.resolve(__dirname, 'node_modules/better-auth/dist/client/react/index.cjs'),
    };
    return config;
  },
};

module.exports = nextConfig;

Expected Behavior

The build should succeed without requiring manual webpack configuration, with webpack properly resolving React hooks from the ESM module.

Additional Context

  • This issue appears to be specific to Next.js webpack builds with React 19
  • The issue does NOT occur when using the CommonJS module
  • transpilePackages: ["better-auth"] alone does not resolve the issue
Originally created by @lohrm-stabl on GitHub (Oct 21, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5458 ## Bug Report: Webpack fails to resolve React hooks from `better-auth/react` with Next.js 15 and React 19 ### Description When building a Next.js 15.4.7 application with React 19.1.0, webpack fails to resolve React hooks (`useRef`, `useSyncExternalStore`) from the `better-auth/react` module, causing the build to fail with import errors. ### Error Message ``` Failed to compile. ./node_modules/better-auth/dist/client/react/index.mjs Attempted import error: 'useRef' is not exported from 'react' (imported as 'useRef'). Import trace for requested module: ./node_modules/better-auth/dist/client/react/index.mjs ./src/lib/auth/config/client.ts ./node_modules/better-auth/dist/client/react/index.mjs Attempted import error: 'useSyncExternalStore' is not exported from 'react' (imported as 'useSyncExternalStore'). Import trace for requested module: ./node_modules/better-auth/dist/client/react/index.mjs ./src/lib/auth/config/client.ts > Build failed because of webpack errors ``` ### Environment - **better-auth version**: 1.3.27 and 1.3.28 (issue occurs on both versions) - **Next.js version**: 15.4.7 - **React version**: 19.1.0 - **React DOM version**: 19.1.0 - **Node.js version**: v22.19.0 - **Package manager**: Bun 1.3.0 - **TypeScript version**: 5.9.3 - **Build command**: `next build` ### Reproduction Steps 1. Create a Next.js 15.4.7 project with React 19.1.0 2. Install `better-auth@1.3.28` or `better-auth@1.3.27` 3. Import and use `createAuthClient` from `better-auth/react`: ```typescript // src/lib/auth/config/client.ts import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ baseURL: "http://localhost:3000", }); ``` 4. Run `next build` 5. Build fails with the error above ### Root Cause Webpack resolves `better-auth/react` to the ESM module (`index.mjs`), which uses named imports from React: ```javascript // node_modules/better-auth/dist/client/react/index.mjs import { useRef, useCallback, useSyncExternalStore } from 'react'; ``` However, webpack's module resolution for the `.mjs` file fails to properly resolve these React exports, even though they are valid and exist in React 19.1.0. ### Observed Behavior - The CommonJS version (`index.cjs`) works correctly and uses `react.useRef` and `react.useSyncExternalStore` - The ESM version (`index.mjs`) fails during webpack compilation - Both files are properly exported in `package.json` exports field ### Workaround Adding a webpack alias in `next.config.js` to force the use of the CommonJS version resolves the issue: ```javascript // next.config.js const { version } = require("./package.json"); const path = require("path"); /** @type {import('next').NextConfig} */ const nextConfig = { publicRuntimeConfig: { version, }, transpilePackages: ["better-auth"], webpack: (config) => { // Alias better-auth/react to use CommonJS version to fix React 19 compatibility config.resolve.alias = { ...config.resolve.alias, 'better-auth/react$': path.resolve(__dirname, 'node_modules/better-auth/dist/client/react/index.cjs'), }; return config; }, }; module.exports = nextConfig; ``` ### Expected Behavior The build should succeed without requiring manual webpack configuration, with webpack properly resolving React hooks from the ESM module. ### Additional Context - This issue appears to be specific to Next.js webpack builds with React 19 - The issue does NOT occur when using the CommonJS module - `transpilePackages: ["better-auth"]` alone does not resolve the issue
GiteaMirror added the lockedbug labels 2026-04-13 06:14:59 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 21, 2025):

This is a known issue affecting Next.js 15 and React 19 when using the ESM build of better-auth/react. Webpack fails to resolve named React hook exports (like useRef and useSyncExternalStore) from the ESM module, even though these exports exist in React 19. The CommonJS build works because it accesses these hooks as properties on the react object, avoiding the named import problem. The ESM and CJS builds are both correctly exported in better-auth's package.json and the source code does not have faulty exports—this appears to be a compatibility issue between Next.js/webpack and React 19 ESM interop, not a bug in better-auth itself [source][source][source].

The recommended workaround is to alias better-auth/react to the CommonJS build in your webpack config, as you've done. There is currently no official fix in better-auth, Next.js, or React for this ESM import issue. If you want to avoid the alias, you could try using dynamic imports with ssr: false for components using these hooks, but this is less reliable. Keep an eye on upstream Next.js and React releases for improvements to ESM interop in the future.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3426645252 --> @dosubot[bot] commented on GitHub (Oct 21, 2025): <!-- Answer --> This is a known issue affecting Next.js 15 and React 19 when using the ESM build of `better-auth/react`. Webpack fails to resolve named React hook exports (like `useRef` and `useSyncExternalStore`) from the ESM module, even though these exports exist in React 19. The CommonJS build works because it accesses these hooks as properties on the `react` object, avoiding the named import problem. The ESM and CJS builds are both correctly exported in `better-auth`'s `package.json` and the source code does not have faulty exports—this appears to be a compatibility issue between Next.js/webpack and React 19 ESM interop, not a bug in `better-auth` itself [[source](https://github.com/better-auth/better-auth/issues/2310)][[source](https://github.com/better-auth/better-auth/issues/1793)][[source](https://github.com/better-auth/better-auth/issues/3123)]. The recommended workaround is to alias `better-auth/react` to the CommonJS build in your webpack config, as you've done. There is currently no official fix in `better-auth`, Next.js, or React for this ESM import issue. If you want to avoid the alias, you could try using dynamic imports with `ssr: false` for components using these hooks, but this is less reliable. Keep an eye on upstream Next.js and React releases for improvements to ESM interop in the future. <!-- 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/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8900baac-ce22-44dc-9b32-dc3f70d1aed3?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/5458)
Author
Owner

@lohrm-stabl commented on GitHub (Oct 21, 2025):

@dosu are there releated issues to this? is this a duplicate?

<!-- gh-comment-id:3426689136 --> @lohrm-stabl commented on GitHub (Oct 21, 2025): @dosu are there releated issues to this? is this a duplicate?
Author
Owner

@dosubot[bot] commented on GitHub (Oct 21, 2025):

Yes, this is a duplicate. There are several open issues in the better-auth repo describing the same problem with webpack failing to resolve React hooks from the ESM build of better-auth/react in Next.js 15 and React 19, including:

All of these discuss the same root cause and recommend the same workaround: alias better-auth/react to the CommonJS build in your webpack config. There’s no official fix yet—this is a compatibility issue between Next.js/webpack and React 19 ESM interop, not a bug in better-auth itself.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3426721595 --> @dosubot[bot] commented on GitHub (Oct 21, 2025): <!-- Answer --> Yes, this is a duplicate. There are several open issues in the better-auth repo describing the same problem with webpack failing to resolve React hooks from the ESM build of better-auth/react in Next.js 15 and React 19, including: - [#1793](https://github.com/better-auth/better-auth/issues/1793) - [#2310](https://github.com/better-auth/better-auth/issues/2310) - [#3123](https://github.com/better-auth/better-auth/issues/3123) All of these discuss the same root cause and recommend the same workaround: alias better-auth/react to the CommonJS build in your webpack config. There’s no official fix yet—this is a compatibility issue between Next.js/webpack and React 19 ESM interop, not a bug in better-auth itself. <!-- 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/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9ac7ca7c-f7ca-4f35-a3c8-4b00cef32f03?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/5458)
Author
Owner

@lohrm-stabl commented on GitHub (Oct 21, 2025):

The solution was mentioned here: https://github.com/facebook/react/issues/33418#issuecomment-3049500092
The authClient created by "better-auth/react" cannot be used in the middleware. Instead use "better-auth/client".

<!-- gh-comment-id:3427193265 --> @lohrm-stabl commented on GitHub (Oct 21, 2025): The solution was mentioned here: https://github.com/facebook/react/issues/33418#issuecomment-3049500092 The authClient created by `"better-auth/react"` cannot be used in the middleware. Instead use `"better-auth/client"`.
Author
Owner

@lohrm-stabl commented on GitHub (Oct 22, 2025):

Okay, nevermind. I just noticed that I dont use the authclient at all in our middleware. We only use it clientside.
If we use better-auth/client instead of better-auth/react, we cannot use useSession() anymore:

./src/components/layout/nav-user.tsx:27:39
Type error: This expression is not callable.
  No constituent of type 'Atom<{ data: { user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ...' is callable.

  25 |
  26 | export default function UserNav({ className }: { className?: string }) {
> 27 |  const { data: session, isPending } = useSession();
Our middleware

import { type NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
import { getLoginUrl } from "./lib/auth/utils";

// This middleware redirects to the login page if the user is not authenticated.
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;

// Liveness probe
if (pathname === "/healthz") {
	return new Response("OK", { status: 200 });
}

// Auth routes are handled by better-auth
if (pathname.startsWith("/api/auth")) {
	return NextResponse.next();
}

// THIS IS NOT SECURE
// https://www.better-auth.com/docs/integrations/next#middleware
// We manually check if the session cookie exists instead of using better-auth helpers
// to avoid importing better-auth/cookies which triggers Edge Runtime warnings.
// Each page has to individually check if the user is authenticated.
const isMaybeAuthenticated = !!getSessionCookie(request);

// Allow /login
// Note: we don't do an authenticated check here, because the page itself should verify the cookie (which the middleware cannot do)
if (["/login"].includes(pathname)) {
	return NextResponse.next();
}

// Redirect to login if not authenticated
if (!isMaybeAuthenticated) {
	const callbackPath =
		pathname !== "/" && pathname !== "/login"
			? pathname + request.nextUrl.search
			: undefined;
	const loginUrl = getLoginUrl(request.url, callbackPath);
	return NextResponse.redirect(loginUrl);
}

// Allow if authenticated - add pathname to headers for server components
const response = NextResponse.next();
response.headers.set("x-pathname", pathname);
return response;

}

export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
/
"/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|background.jpeg).
)",
],
};

<!-- gh-comment-id:3432371524 --> @lohrm-stabl commented on GitHub (Oct 22, 2025): Okay, nevermind. I just noticed that I dont use the authclient at all in our middleware. We only use it clientside. If we use `better-auth/client` instead of `better-auth/react`, we cannot use `useSession()` anymore: ```typescript ./src/components/layout/nav-user.tsx:27:39 Type error: This expression is not callable. No constituent of type 'Atom<{ data: { user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined; banned: boolean | null | undefined; role?: string | ... 1 more ... | undefined; banReason?: string | ... 1 more ... | undefined; banExpires?: Date | ... 1 more ...' is callable. 25 | 26 | export default function UserNav({ className }: { className?: string }) { > 27 | const { data: session, isPending } = useSession(); ``` <details> <summary>Our middleware</summary> <pre><code> import { type NextRequest, NextResponse } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; import { getLoginUrl } from "./lib/auth/utils"; // This middleware redirects to the login page if the user is not authenticated. export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // Liveness probe if (pathname === "/healthz") { return new Response("OK", { status: 200 }); } // Auth routes are handled by better-auth if (pathname.startsWith("/api/auth")) { return NextResponse.next(); } // THIS IS NOT SECURE // https://www.better-auth.com/docs/integrations/next#middleware // We manually check if the session cookie exists instead of using better-auth helpers // to avoid importing better-auth/cookies which triggers Edge Runtime warnings. // Each page has to individually check if the user is authenticated. const isMaybeAuthenticated = !!getSessionCookie(request); // Allow /login // Note: we don't do an authenticated check here, because the page itself should verify the cookie (which the middleware cannot do) if (["/login"].includes(pathname)) { return NextResponse.next(); } // Redirect to login if not authenticated if (!isMaybeAuthenticated) { const callbackPath = pathname !== "/" && pathname !== "/login" ? pathname + request.nextUrl.search : undefined; const loginUrl = getLoginUrl(request.url, callbackPath); return NextResponse.redirect(loginUrl); } // Allow if authenticated - add pathname to headers for server components const response = NextResponse.next(); response.headers.set("x-pathname", pathname); return response; } export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico, sitemap.xml, robots.txt (metadata files) */ "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|background.jpeg).*)", ], }; </code></pre> </details>
Author
Owner

@himself65 commented on GitHub (Oct 23, 2025):

useSession from better-auth/client is not a function. So you cannot call it as expcted

<!-- gh-comment-id:3439886368 --> @himself65 commented on GitHub (Oct 23, 2025): useSession from `better-auth/client` is not a function. So you cannot call it as expcted
Author
Owner

@himself65 commented on GitHub (Oct 23, 2025):

Sorry, I think this is kinda your bundler issue. We do ship the CJS module for the 1.3 version. Cound you please provde repo to reproduce?

<!-- gh-comment-id:3439925168 --> @himself65 commented on GitHub (Oct 23, 2025): Sorry, I think this is kinda your bundler issue. We do ship the CJS module for the 1.3 version. Cound you please provde repo to reproduce?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10252