Skip to main content
This guide covers the most impactful techniques for improving the performance of a Better Auth application.

Session caching with cookies

By default, every call to getSession or useSession hits the database. Cookie caching stores a short-lived signed copy of the session in the browser, eliminating the database round-trip for repeated reads.
auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // 5 minutes
    },
  },
});
This is analogous to using a short-lived JWT access token alongside a refresh token. The session is re-validated from the database after maxAge seconds. Read more in the session management docs.

Framework-level caching

Use the "use cache" directive (Next.js 15+) on server functions that return user lists or other infrequently-changing data:
export async function getUsers() {
  "use cache";
  const { users } = await auth.api.listUsers();
  return users;
}
Learn more in the Next.js use cache docs.

SSR session prefetching

Pre-fetch the session on the server and pass it as initial data to the client to avoid a waterfall request:
// In a server component or loader
const session = await auth.api.getSession({
  headers: await headers(),
});

// Pass `session` as a prop or via a data store to the client

Background tasks

On serverless platforms, non-critical work like cleanup, analytics, and email can run after the response is sent using the backgroundTasks option. This reduces perceived latency without sacrificing correctness.
auth.ts
import { betterAuth } from "better-auth";
import { createAuthMiddleware } from "better-auth/api";
import { waitUntil } from "@vercel/functions";

export const auth = betterAuth({
  advanced: {
    backgroundTasks: { handler: waitUntil },
  },
  hooks: {
    after: createAuthMiddleware(async (ctx) => {
      if (ctx.path === "/sign-up/email") {
        ctx.context.runInBackground(
          sendWelcomeEmail(ctx.context.newSession?.user.id),
        );
      }
    }),
  },
});
See the backgroundTasks option and the hooks docs for Cloudflare Workers examples.
Background tasks introduce eventual consistency: the response returns before deferred work completes. Only use this when your application can tolerate brief inconsistency.

Database indexing

Adding indexes to the core tables has the highest impact at scale. The table below lists the fields that benefit most from indexes:
TableFields to indexPlugin
usersemail
accountsuserId
sessionsuserId, token
verificationsidentifier
invitationsemail, organizationIdorganization
membersuserId, organizationIdorganization
organizationsslugorganization
passkeyuserIdpasskey
twoFactorsecrettwo-factor
Index support in the generate / migrate CLI commands is planned for a future release.

Bundle size optimization

If you are using a custom ORM adapter (Prisma, Drizzle, MongoDB) you can reduce your server bundle by importing from better-auth/minimal. This variant omits the bundled Kysely dependency.
auth.ts
import { betterAuth } from "better-auth/minimal";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export const auth = betterAuth({
  database: prismaAdapter(prisma, { provider: "postgresql" }),
});
better-auth/minimal does not support direct database connections or built-in migrations. Use a full better-auth import if you need those features.

Rate limiting

Better Auth includes built-in rate limiting. In high-traffic scenarios, using secondaryStorage (Redis, Cloudflare KV) for rate-limit counters instead of in-memory storage avoids state loss across serverless invocations:
rateLimit: {
  enabled: true,
  storage: "secondary-storage",
  window: 10,
  max: 100,
  customRules: {
    "/sign-in/email": { window: 10, max: 5 },
  },
}