perf(auth): memoize auth() per request with React cache

A single authenticated render called auth() 4-5 times (access guard, site
layout, both permission loaders, the page itself), and every call re-parsed
the session cookie, decoded and HMAC-verified the JWT and ran the jwt/session
callbacks. Wrapping NextAuth's handler in React's cache scopes that work to
one verification per request.

Outside a render (scripts, jobs worker) React's cache is a pass-through, so
those call sites behave exactly as before.
This commit is contained in:
openhands committed 2026-10-18 14:49:34 +02:00
1 parent 095353012a
commit d28dde459a
1 file changed
+14 -1
+14 -1
View File
@@ -1,6 +1,7 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import NextAuth from "next-auth"; import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials"; import Credentials from "next-auth/providers/credentials";
import { cache } from "react";
import { env } from "@/env"; import { env } from "@/env";
import { getCachedJwtVersion } from "@/lib/auth/jwt-version-cache"; import { getCachedJwtVersion } from "@/lib/auth/jwt-version-cache";
import { import {
@@ -25,7 +26,7 @@ import { db, User } from "@/lib/db";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
import { clientIp, rateLimit } from "@/lib/rate-limit"; import { clientIp, rateLimit } from "@/lib/rate-limit";
export const { handlers, signOut, auth } = NextAuth({ const nextAuth = NextAuth({
trustHost: true, trustHost: true,
secret: env.AUTH_SECRET, secret: env.AUTH_SECRET,
session: { strategy: "jwt", maxAge: 24 * 60 * 60 }, session: { strategy: "jwt", maxAge: 24 * 60 * 60 },
@@ -161,3 +162,15 @@ export const { handlers, signOut, auth } = NextAuth({
}, },
}, },
}); });
export const { handlers, signOut } = nextAuth;
/**
* `auth()` re-parses the session cookie, decodes and HMAC-verifies the JWT and
* runs the `jwt`/`session` callbacks on every call. A single authenticated
* render calls it from the access guard, the site layout, both permission
* loaders and the page itself — 4-5 times on the critical path. React's
* `cache` scopes the memo to the current request, so those all share one
* verification. Outside a render (scripts, worker) it is a pass-through.
*/
export const auth = cache(nextAuth.auth);