I'm having issues setting an basePath with Nextjs and Better-auth #1945

Closed
opened 2026-03-13 09:14:11 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @FerreiraRaphael on GitHub (Sep 17, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

To Reproduce

Dependencies:
"next": "15.4.1",
"better-auth": "^1.3.9",

better-auth api path

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

next.config.ts

const nextConfig: NextConfig = {
  basePath: '/tabela-de-vendas-nova',
  redirects: async () => {
    return [
      {
        source: '/',
        destination: '/tabela-de-vendas-nova',
        basePath: false,
        permanent: true,
      },
    ];
  },
  /* config options here */
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'res.cloudinary.com',
      },
    ],
  },
  serverExternalPackages: ['import-in-the-middle'],
};

auth.client.ts

import { organizationClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";

import { ac, admin, gestor, owner } from "./permissions";

export const authClient = createAuthClient({
  baseUrl: `http://localhost:3000/tabela-de-vendas-nova/api/auth`,
  // basePath: `/tabela-de-vendas-nova/api/auth`,
  plugins: [
    organizationClient({
      ac,
      roles: {
        gestor,
        admin,
        owner,
      },
    }),
  ],
});

Current vs. Expected behavior

I keep getting 404 on the better-auth URLs locally.
The URLs are not found!

Expect the urls to not return 404

What version of Better Auth are you using?

1.3.9

System info

System:
    OS: MAC M3
    CPU: m3
    Memory: 18gb
  Browsers:
    Chrome

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

Client, Backend

Auth config (if applicable)

export const auth = betterAuth({
  baseURL: `http://localhost:3000/tabela-de-vendas-nova/api/auth`,
  // basePath: `/tabela-de-vendas-nova/api/auth`,
  emailAndPassword: {
    enabled: true,
  },
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  plugins: [
    organization({
      ac,
      roles: {
        gestor,
        admin,
        owner,
      },
    }),
  ],
});

Additional context

No response

Originally created by @FerreiraRaphael on GitHub (Sep 17, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ## To Reproduce Dependencies: "next": "15.4.1", "better-auth": "^1.3.9", ### better-auth api path `app/api/auth/[...all]/route.ts` ### next.config.ts ```typescript const nextConfig: NextConfig = { basePath: '/tabela-de-vendas-nova', redirects: async () => { return [ { source: '/', destination: '/tabela-de-vendas-nova', basePath: false, permanent: true, }, ]; }, /* config options here */ images: { remotePatterns: [ { protocol: 'https', hostname: 'res.cloudinary.com', }, ], }, serverExternalPackages: ['import-in-the-middle'], }; ``` ### auth.client.ts ```typescript import { organizationClient } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; import { ac, admin, gestor, owner } from "./permissions"; export const authClient = createAuthClient({ baseUrl: `http://localhost:3000/tabela-de-vendas-nova/api/auth`, // basePath: `/tabela-de-vendas-nova/api/auth`, plugins: [ organizationClient({ ac, roles: { gestor, admin, owner, }, }), ], }); ``` ### Current vs. Expected behavior I keep getting 404 on the better-auth URLs locally. The URLs are not found! Expect the urls to not return 404 ### What version of Better Auth are you using? 1.3.9 ### System info ```bash System: OS: MAC M3 CPU: m3 Memory: 18gb Browsers: Chrome ``` ### Which area(s) are affected? (Select all that apply) Client, Backend ### Auth config (if applicable) ```typescript export const auth = betterAuth({ baseURL: `http://localhost:3000/tabela-de-vendas-nova/api/auth`, // basePath: `/tabela-de-vendas-nova/api/auth`, emailAndPassword: { enabled: true, }, database: drizzleAdapter(db, { provider: "pg", }), plugins: [ organization({ ac, roles: { gestor, admin, owner, }, }), ], }); ``` ### Additional context _No response_
GiteaMirror added the nextjsbug labels 2026-03-13 09:14:11 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 17, 2025):

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

The 404 errors are likely due to a mismatch between your Next.js basePath and the Better Auth API/client configuration. When using a custom basePath in Next.js, you must set the full API path in Better Auth’s baseURL option—this means both your client and server configs should use baseURL: 'http://localhost:3000/tabela-de-vendas-nova/api/auth'. If you set baseURL with a path, Better Auth ignores its own basePath option, which is expected behavior (source).

Make sure your API route handler is at app/api/auth/[...all]/route.ts and uses toNextJsHandler(auth) (source). Also, double-check that all client requests and OAuth redirect URIs include the basePath (e.g., /tabela-de-vendas-nova/api/auth/callback/google). A common cause of 404s is the client requesting /api/auth/get-session instead of /tabela-de-vendas-nova/api/auth/get-session (source).

If you follow this setup and still get 404s, confirm that your Next.js app is running with the correct basePath and that your API route is accessible at the expected URL. There are no recent breaking changes in Better Auth affecting this integration.

To reply, just mention @dosu.


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

@dosubot[bot] commented on GitHub (Sep 17, 2025): <!-- Greeting --> Hi @FerreiraRaphael! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The 404 errors are likely due to a mismatch between your Next.js basePath and the Better Auth API/client configuration. When using a custom basePath in Next.js, you must set the full API path in Better Auth’s baseURL option—this means both your client and server configs should use `baseURL: 'http://localhost:3000/tabela-de-vendas-nova/api/auth'`. If you set baseURL with a path, Better Auth ignores its own basePath option, which is expected behavior ([source](https://github.com/better-auth/better-auth/issues/2710)). Make sure your API route handler is at `app/api/auth/[...all]/route.ts` and uses `toNextJsHandler(auth)` ([source](https://github.com/better-auth/better-auth/blob/5359f32778633dc7c46809de6b573016655fac5d/demo/nextjs/app/api/auth/[...all]/route.ts)). Also, double-check that all client requests and OAuth redirect URIs include the basePath (e.g., `/tabela-de-vendas-nova/api/auth/callback/google`). A common cause of 404s is the client requesting `/api/auth/get-session` instead of `/tabela-de-vendas-nova/api/auth/get-session` ([source](https://github.com/better-auth/better-auth/issues/4423)). If you follow this setup and still get 404s, confirm that your Next.js app is running with the correct basePath and that your API route is accessible at the expected URL. There are no recent breaking changes in Better Auth affecting this integration. <!-- 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/a0982b92-2686-442e-8919-3f9b5f1cb858?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a0982b92-2686-442e-8919-3f9b5f1cb858?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a0982b92-2686-442e-8919-3f9b5f1cb858?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a0982b92-2686-442e-8919-3f9b5f1cb858?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a0982b92-2686-442e-8919-3f9b5f1cb858?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a0982b92-2686-442e-8919-3f9b5f1cb858?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a0982b92-2686-442e-8919-3f9b5f1cb858?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/4715)
Author
Owner

@FerreiraRaphael commented on GitHub (Sep 17, 2025):

I can confirm that all my configurations are correct, as I posted on the thread.

But I keep getting 404 on the has-permission endpoint, for example.
http://localhost:3000/tabela-de-vendas-nova/api/auth/organization/has-permission

My route.ts file follows the basic example from the docs:

import { toNextJsHandler } from "better-auth/next-js";

import { auth } from "@/lib/auth";

export const { GET, POST } = toNextJsHandler(auth.handler);

Both auth.client.ts and auth.ts have
baseURL: http://localhost:3000/tabela-de-vendas-nova/api/auth,

next.config.ts has the basePath set to /tabela-de-vendas-nova

@FerreiraRaphael commented on GitHub (Sep 17, 2025): I can confirm that all my configurations are correct, as I posted on the thread. But I keep getting 404 on the has-permission endpoint, for example. http://localhost:3000/tabela-de-vendas-nova/api/auth/organization/has-permission My `route.ts` file follows the basic example from the docs: ``` import { toNextJsHandler } from "better-auth/next-js"; import { auth } from "@/lib/auth"; export const { GET, POST } = toNextJsHandler(auth.handler); ``` Both `auth.client.ts` and `auth.ts` have baseURL: `http://localhost:3000/tabela-de-vendas-nova/api/auth`, `next.config.ts` has the basePath set to `/tabela-de-vendas-nova`
Author
Owner

@dosubot[bot] commented on GitHub (Sep 17, 2025):

The 404 on /organization/has-permission is almost always caused by a mismatch between the runtime URL, the Next.js basePath, and the Better Auth config. With your setup, the handler at app/api/auth/[...all]/route.ts is correct—Next.js will map /tabela-de-vendas-nova/api/auth/* to that file, so you do not need to nest under the basePath directory (demo reference).

Here’s what to check next:

  • Make sure you’re exporting GET and POST directly from toNextJsHandler(auth) as in the demo. Avoid assigning to an intermediate variable or using a different export pattern—this can cause 404/405 errors (source).
  • Double-check that both your server and client configs use the exact same baseURL, including the basePath and /api/auth segment (source).
  • Clear your .next build cache and restart your dev server after any config or route changes.
  • Make sure there are no other files or routes shadowing app/api/auth/[...all]/route.ts.
  • If you have any custom Next.js middleware, rewrites, or redirects, confirm they aren’t interfering with /tabela-de-vendas-nova/api/auth/* requests.

If all of the above is correct and you still get 404s, try temporarily replacing your route file with the official Better Auth demo code to isolate the issue. If the problem persists, there may be an environment-specific conflict or a subtle config drift.

To reply, just mention @dosu.


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

@dosubot[bot] commented on GitHub (Sep 17, 2025): <!-- Answer --> The 404 on `/organization/has-permission` is almost always caused by a mismatch between the runtime URL, the Next.js basePath, and the Better Auth config. With your setup, the handler at `app/api/auth/[...all]/route.ts` is correct—Next.js will map `/tabela-de-vendas-nova/api/auth/*` to that file, so you do not need to nest under the basePath directory ([demo reference](https://github.com/better-auth/better-auth/blob/5359f32778633dc7c46809de6b573016655fac5d/demo/nextjs/app/api/auth/%5B...all%5D/route.ts)). Here’s what to check next: - Make sure you’re exporting GET and POST directly from `toNextJsHandler(auth)` as in the demo. Avoid assigning to an intermediate variable or using a different export pattern—this can cause 404/405 errors ([source](https://github.com/better-auth/better-auth/issues/3684#issuecomment-3130410892)). - Double-check that both your server and client configs use the exact same baseURL, including the basePath and `/api/auth` segment ([source](https://github.com/better-auth/better-auth/issues/2710)). - Clear your `.next` build cache and restart your dev server after any config or route changes. - Make sure there are no other files or routes shadowing `app/api/auth/[...all]/route.ts`. - If you have any custom Next.js middleware, rewrites, or redirects, confirm they aren’t interfering with `/tabela-de-vendas-nova/api/auth/*` requests. If all of the above is correct and you still get 404s, try temporarily replacing your route file with the official Better Auth demo code to isolate the issue. If the problem persists, there may be an environment-specific conflict or a subtle config drift. <!-- 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/abef986e-6a1f-4162-96a7-990a48785d33?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/abef986e-6a1f-4162-96a7-990a48785d33?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/abef986e-6a1f-4162-96a7-990a48785d33?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/abef986e-6a1f-4162-96a7-990a48785d33?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/abef986e-6a1f-4162-96a7-990a48785d33?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/abef986e-6a1f-4162-96a7-990a48785d33?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/abef986e-6a1f-4162-96a7-990a48785d33?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/4715)
Author
Owner

@FerreiraRaphael commented on GitHub (Sep 17, 2025):

After debugging the request object passed by NextJS in the request function, I found out that for some reason the request dont have the url with the basePath included lol.

So I call the api with http://localhost:3000/tabela-de-vendas-nova/api/auth/get-session
But the request URL in the request obj is http://localhost:3000/api/auth/get-session

So the auth.ts object, which is set to baseUrl http://localhost:3000/tabela-de-vendas-nova/api/auth throws 404 error...

So my solution was:

Set the base URL in the auth client with the basePath and the auth.ts without the base path, now it works.

auth.client.ts
baseURL: http://localhost:3000/tabela-de-vendas-nova/api/auth,

auth.ts
baseURL: http://localhost:3000/api/auth,

Hope this helps someone hehe

@FerreiraRaphael commented on GitHub (Sep 17, 2025): After debugging the request object passed by NextJS in the request function, I found out that for some reason the request dont have the url with the basePath included lol. So I call the api with `http://localhost:3000/tabela-de-vendas-nova/api/auth/get-session` But the request URL in the request obj is `http://localhost:3000/api/auth/get-session` So the `auth.ts` object, which is set to baseUrl `http://localhost:3000/tabela-de-vendas-nova/api/auth` throws 404 error... So my solution was: Set the base URL in the auth client with the basePath and the `auth.ts` without the base path, now it works. `auth.client.ts` baseURL: `http://localhost:3000/tabela-de-vendas-nova/api/auth`, `auth.ts` baseURL: `http://localhost:3000/api/auth`, Hope this helps someone hehe
Author
Owner

@koslib commented on GitHub (Sep 27, 2025):

Dealing with the same issue actually. @FerreiraRaphael, your workaround works, but it'd be great to know of a less-hacky way to fix this.

Anyone from the better-auth team can potentially weight in here?

@koslib commented on GitHub (Sep 27, 2025): Dealing with the same issue actually. @FerreiraRaphael, your workaround works, but it'd be great to know of a less-hacky way to fix this. Anyone from the better-auth team can potentially weight in here?
Author
Owner

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

You need to do something like this:

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

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

const handlers = toNextJsHandler(authServer);

# better to use an .env variable
const baseURL = 'http://localhost:3000/tabela-de-vendas-nova' 

function rewriteRequest(req: NextRequest) {
  const { search, pathname } = req.nextUrl;
  const url = new URL(`${baseURL}${pathname}`)
  url.search = search

  return new NextRequest(url, req);
}

export async function GET(req: NextRequest) {
    const modified = rewriteRequest(req)
    return handlers.GET(modified);
}

export async function POST(req: NextRequest) {
    const modified = rewriteRequest(req)
    return handlers.POST(modified)
}

auth-client.ts:

export const authClient = createAuthClient({
    baseURL: 'http://localhost:3000/tabela-de-vendas-nova/api/auth'
    plugins: ...
});

auth-server.ts:

export const authServer = betterAuth({
    baseURL: 'http://localhost:3000/tabela-de-vendas-nova/api/auth'
    database: ...
    plugins: ...
})

next.config.ts

const nextConfig: NextConfig = {
    basePath: '/tabela-de-vendas-nova',
    assetPrefix: '/tabela-de-vendas-nova',
    ...
};

I read about it somewhere on the auth.js discussions and it seems to works with better-auth as well. The url inside of the NextRequest uses the internal url, with no basepath applied. Why doesnt toNextJsHandle do this automatically...? I have no clue.

Example:

URL you use to connect to the site:

  • www.website.com/subdirectory/api/auth/get-session

  • localhost:3000/subdirectory/api/auth/get-session

URL that nextrequest sees:

  • localhost:3000/api/auth/get-session.
@ericOrthoshop commented on GitHub (Oct 23, 2025): You need to do something like this: **api/auth/[...all]/route.ts** ``` import { authServer } from "@/lib/auth-server"; import { toNextJsHandler } from "better-auth/next-js"; import { NextRequest } from "next/server"; const handlers = toNextJsHandler(authServer); # better to use an .env variable const baseURL = 'http://localhost:3000/tabela-de-vendas-nova' function rewriteRequest(req: NextRequest) { const { search, pathname } = req.nextUrl; const url = new URL(`${baseURL}${pathname}`) url.search = search return new NextRequest(url, req); } export async function GET(req: NextRequest) { const modified = rewriteRequest(req) return handlers.GET(modified); } export async function POST(req: NextRequest) { const modified = rewriteRequest(req) return handlers.POST(modified) } ``` **auth-client.ts:** ``` export const authClient = createAuthClient({ baseURL: 'http://localhost:3000/tabela-de-vendas-nova/api/auth' plugins: ... }); ``` **auth-server.ts:** ``` export const authServer = betterAuth({ baseURL: 'http://localhost:3000/tabela-de-vendas-nova/api/auth' database: ... plugins: ... }) ``` **next.config.ts** ``` const nextConfig: NextConfig = { basePath: '/tabela-de-vendas-nova', assetPrefix: '/tabela-de-vendas-nova', ... }; ``` I read about it somewhere on the auth.js discussions and it seems to works with better-auth as well. The url inside of the NextRequest uses the internal url, with no basepath applied. Why doesnt toNextJsHandle do this automatically...? I have no clue. **Example:** URL you use to connect to the site: - www.website.com/subdirectory/api/auth/get-session - localhost:3000/subdirectory/api/auth/get-session URL that nextrequest sees: - localhost:3000/api/auth/get-session.
Author
Owner

@marcoseiza commented on GitHub (Jan 29, 2026):

I used the following plugin to fix this

/**
* `baseUrl` is the full path of your client `http://localhost:3000/base/path`
*/
const nextBasePathMiddleware = (baseUrl: string) => ({
      id: "next-base-path-middleware",
      onRequest: async (request) => {
        if ("nextUrl" in request) {
          const { search, pathname } = (request as NextRequest).nextUrl;
          const url = new URL(`${basePath}${pathname}`);
          url.search = search;
          return { request: new NextRequest(url, request) };
        }
      },
    })
const AUTH_PATH = "/api/auth"
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL! + AUTH_PATH;
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH! + AUTH_PATH;

export const auth = betterAuth({
  secret: SECRET,
  baseURL: BASE_URL,
  basePath: BASE_PATH,

  plugins: [
    nextBasePathMiddleware(process.env.NEXT_PUBLIC_BASE_URL),

    // Make sure that this is the the last plugin in the array.
    nextCookies(),
  ],
});
@marcoseiza commented on GitHub (Jan 29, 2026): I used the following plugin to fix this ```ts /** * `baseUrl` is the full path of your client `http://localhost:3000/base/path` */ const nextBasePathMiddleware = (baseUrl: string) => ({ id: "next-base-path-middleware", onRequest: async (request) => { if ("nextUrl" in request) { const { search, pathname } = (request as NextRequest).nextUrl; const url = new URL(`${basePath}${pathname}`); url.search = search; return { request: new NextRequest(url, request) }; } }, }) ``` ```ts const AUTH_PATH = "/api/auth" const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL! + AUTH_PATH; const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH! + AUTH_PATH; export const auth = betterAuth({ secret: SECRET, baseURL: BASE_URL, basePath: BASE_PATH, plugins: [ nextBasePathMiddleware(process.env.NEXT_PUBLIC_BASE_URL), // Make sure that this is the the last plugin in the array. nextCookies(), ], }); ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1945