[GH-ISSUE #5613] Prerendering issue with Next.js 16 App Router when using getSession in API route #18932

Closed
opened 2026-04-15 17:39:58 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @tarifalhasan on GitHub (Oct 27, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5613

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

When using Better Auth in a Next.js 16 project with the App Router, API routes that use getSession (which relies on request.headers or cookies()) are causing prerendering to fail. The error indicates that the route needs to bail out of prerendering because it used request.headers.

We have tried marking the route as dynamic = 'force-dynamic' and runtime = 'nodejs' but the issue persists.

Steps to Reproduce:

Set up a Next.js 16 project with the App Router.

Configure Better Auth with the Prisma adapter and the next-js plugin.

Create an API route that uses getSession to check authentication.

Try to access the API route and observe the error.

Code Snippets:

/lib/auth.ts (Better Auth configuration):

`import { PrismaClient } from "@/prisma/generated/prisma";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import { admin as adminPlugin } from "better-auth/plugins";
import { ac, admin, doctor, user } from "./permissions";

const prisma = new PrismaClient();

export const auth = betterAuth({
database: prismaAdapter(prisma, { provider: "mongodb" }),
emailAndPassword: {
enabled: true,
async sendResetPassword({ user, url }) {},
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
},
},
advanced: {
defaultCookieAttributes: {
sameSite: "none",
secure: true,
httpOnly: true,
},
},
user: {
additionalFields: {
role: {
type: "string",
required: false,
defaultValue: "user",
},
},
},
plugins: [
adminPlugin({
adminUserIds: ["BmiEQBumD31Kv4TWFipaoqMnmMGmRFsV"],
ac: ac,
adminRoles: ["admin", "user", "doctor"],
roles: {
admin,
user,
doctor,
},
}),

nextCookies(),

],
});

export type Session = typeof auth.$Infer.Session;
export type User = typeof auth.$Infer.Session.user;
`
/lib/get-session.ts (session retrieval):

`import { auth } from "./auth";

export async function getServerSession(cookieHeader: string) {
return await auth.api.getSession({ headers: { Cookie: cookieHeader } });
}
`

/app/api/user/appointments/route.ts (API route):

`import { getServerSession } from "@/lib/get-session";

import { PrismaClient } from "@/prisma/generated/prisma";
import { cookies } from "next/headers";

import { NextRequest, NextResponse } from "next/server";

const prisma = new PrismaClient();

export async function GET(request: NextRequest) {
try {
const cookieHeader = (await cookies()).toString();
const session = await getServerSession(cookieHeader);

if (!session) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}


return NextResponse.json({});

} catch (error) {
console.error("Error fetching user appointments:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
`

Current vs. Expected behavior

Error fetching user appointments: Error: Route /api/user/appointments needs to bail out of prerendering at this point because it used request.headers.
at C (turbopack:///[project]/app/api/user/appointments/route.ts:11:34)
9 | export async function GET(request: NextRequest) {
10 | try {

11 | const cookieHeader = request.headers.get("cookie") || "";
| ^
12 | const session = await getServerSessionV2(cookieHeader);
13 |
14 | if (!session) { {
digest: 'NEXT_PRERENDER_INTERRUPTED'

What version of Better Auth are you using?

^1.3.32

System info

import { auth } from "./auth";

export async function getServerSession(cookieHeader: string) {
  return await auth.api.getSession({ headers: { Cookie: cookieHeader } });
}

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

Client, Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

import { PrismaClient } from "@/prisma/generated/prisma";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import { admin as adminPlugin } from "better-auth/plugins";
import { ac, admin, doctor, user } from "./permissions";

const prisma = new PrismaClient();

export const auth = betterAuth({
database: prismaAdapter(prisma, { provider: "mongodb" }),
emailAndPassword: {
enabled: true,
async sendResetPassword({ user, url }) {},
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
},
},
advanced: {
defaultCookieAttributes: {
sameSite: "none",
secure: true,
httpOnly: true,
},
},
user: {
additionalFields: {
role: {
type: "string",
required: false,
defaultValue: "user",
},
},
},
plugins: [
adminPlugin({
adminUserIds: ["BmiEQBumD31Kv4TWFipaoqMnmMGmRFsV"],
ac: ac,
adminRoles: ["admin", "user", "doctor"],
roles: {
admin,
user,
doctor,
},
}),

nextCookies(),

],
});

export type Session = typeof auth.$Infer.Session;
export type User = typeof auth.$Infer.Session.user;

Originally created by @tarifalhasan on GitHub (Oct 27, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5613 ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce When using Better Auth in a Next.js 16 project with the App Router, API routes that use getSession (which relies on request.headers or cookies()) are causing prerendering to fail. The error indicates that the route needs to bail out of prerendering because it used request.headers. We have tried marking the route as dynamic = 'force-dynamic' and runtime = 'nodejs' but the issue persists. Steps to Reproduce: Set up a Next.js 16 project with the App Router. Configure Better Auth with the Prisma adapter and the next-js plugin. Create an API route that uses getSession to check authentication. Try to access the API route and observe the error. Code Snippets: /lib/auth.ts (Better Auth configuration): `import { PrismaClient } from "@/prisma/generated/prisma"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { nextCookies } from "better-auth/next-js"; import { admin as adminPlugin } from "better-auth/plugins"; import { ac, admin, doctor, user } from "./permissions"; const prisma = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "mongodb" }), emailAndPassword: { enabled: true, async sendResetPassword({ user, url }) {}, }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, }, advanced: { defaultCookieAttributes: { sameSite: "none", secure: true, httpOnly: true, }, }, user: { additionalFields: { role: { type: "string", required: false, defaultValue: "user", }, }, }, plugins: [ adminPlugin({ adminUserIds: ["BmiEQBumD31Kv4TWFipaoqMnmMGmRFsV"], ac: ac, adminRoles: ["admin", "user", "doctor"], roles: { admin, user, doctor, }, }), nextCookies(), ], }); export type Session = typeof auth.$Infer.Session; export type User = typeof auth.$Infer.Session.user; ` /lib/get-session.ts (session retrieval): `import { auth } from "./auth"; export async function getServerSession(cookieHeader: string) { return await auth.api.getSession({ headers: { Cookie: cookieHeader } }); } ` /app/api/user/appointments/route.ts (API route): `import { getServerSession } from "@/lib/get-session"; import { PrismaClient } from "@/prisma/generated/prisma"; import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; const prisma = new PrismaClient(); export async function GET(request: NextRequest) { try { const cookieHeader = (await cookies()).toString(); const session = await getServerSession(cookieHeader); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } return NextResponse.json({}); } catch (error) { console.error("Error fetching user appointments:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 } ); } } ` ### Current vs. Expected behavior Error fetching user appointments: Error: Route /api/user/appointments needs to bail out of prerendering at this point because it used request.headers. at C (turbopack:///[project]/app/api/user/appointments/route.ts:11:34) 9 | export async function GET(request: NextRequest) { 10 | try { > 11 | const cookieHeader = request.headers.get("cookie") || ""; | ^ 12 | const session = await getServerSessionV2(cookieHeader); 13 | 14 | if (!session) { { digest: 'NEXT_PRERENDER_INTERRUPTED' ### What version of Better Auth are you using? ^1.3.32 ### System info ```bash import { auth } from "./auth"; export async function getServerSession(cookieHeader: string) { return await auth.api.getSession({ headers: { Cookie: cookieHeader } }); } ``` ### Which area(s) are affected? (Select all that apply) Client, Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context import { PrismaClient } from "@/prisma/generated/prisma"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { nextCookies } from "better-auth/next-js"; import { admin as adminPlugin } from "better-auth/plugins"; import { ac, admin, doctor, user } from "./permissions"; const prisma = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "mongodb" }), emailAndPassword: { enabled: true, async sendResetPassword({ user, url }) {}, }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, }, advanced: { defaultCookieAttributes: { sameSite: "none", secure: true, httpOnly: true, }, }, user: { additionalFields: { role: { type: "string", required: false, defaultValue: "user", }, }, }, plugins: [ adminPlugin({ adminUserIds: ["BmiEQBumD31Kv4TWFipaoqMnmMGmRFsV"], ac: ac, adminRoles: ["admin", "user", "doctor"], roles: { admin, user, doctor, }, }), nextCookies(), ], }); export type Session = typeof auth.$Infer.Session; export type User = typeof auth.$Infer.Session.user;
GiteaMirror added the locked label 2026-04-15 17:39:58 -05:00
Author
Owner

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

This is a known issue: starting with Better Auth v1.3.8, there's a regression that causes prerendering to fail when calling getSession (especially authClient.getSession) in server-side or prerendered routes, including with Next.js 16 App Router. This is tracked in issue #5020.

The root cause is that getSession relies on request-specific data (headers/cookies), which is incompatible with static prerendering. Marking the route as dynamic = 'force-dynamic' and runtime = 'nodejs' doesn't always help if the code still tries to access request headers or cookies during static generation—this is a Next.js limitation, not just a Better Auth one [source].

Workarounds and recommendations:

  • For server-side session retrieval, always use auth.api.getSession() and pass the correct headers (like cookies), not authClient.getSession() (which is for client-side use).
  • Make sure your API routes are truly dynamic and not statically rendered.
  • If you need a quick fix, downgrading to Better Auth v1.3.7 avoids this regression [source].
  • Some users proxy session requests through API routes and forward cookies, or use server actions that fetch session data from a remote Better Auth server, rather than calling getSession directly in a static context [source].

This is a real bug/regression, not just a usage issue, and is already tracked in the repo. For updates and more details, follow issue #5020 and related discussions.

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:3451340138 --> @dosubot[bot] commented on GitHub (Oct 27, 2025): <!-- Answer --> This is a known issue: starting with Better Auth v1.3.8, there's a regression that causes prerendering to fail when calling getSession (especially authClient.getSession) in server-side or prerendered routes, including with Next.js 16 App Router. This is tracked in [issue #5020](https://github.com/better-auth/better-auth/issues/5020). The root cause is that getSession relies on request-specific data (headers/cookies), which is incompatible with static prerendering. Marking the route as `dynamic = 'force-dynamic'` and `runtime = 'nodejs'` doesn't always help if the code still tries to access request headers or cookies during static generation—this is a Next.js limitation, not just a Better Auth one [[source]](https://github.com/better-auth/better-auth/issues/5020#issuecomment-3357123011). **Workarounds and recommendations:** - For server-side session retrieval, always use `auth.api.getSession()` and pass the correct headers (like cookies), not `authClient.getSession()` (which is for client-side use). - Make sure your API routes are truly dynamic and not statically rendered. - If you need a quick fix, downgrading to Better Auth v1.3.7 avoids this regression [[source]](https://github.com/better-auth/better-auth/issues/5020#issuecomment-3357123011). - Some users proxy session requests through API routes and forward cookies, or use server actions that fetch session data from a remote Better Auth server, rather than calling getSession directly in a static context [[source]](https://github.com/better-auth/better-auth/issues/4044). This is a real bug/regression, not just a usage issue, and is already tracked in the repo. For updates and more details, follow [issue #5020](https://github.com/better-auth/better-auth/issues/5020) and related discussions. <!-- 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/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/f8e7ed15-0408-4a45-a5ab-6bf3247d939f?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/5613)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#18932