From 04150c1f341290caab3eb754270a00304eceb708 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 12:07:13 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Implement=20request=20memoi?= =?UTF-8?q?zation=20using=20React=20cache()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented request memoization for core authentication and data fetching functions to deduplicate database queries and JWT verifications within a single request lifecycle. - Memoized `verifyToken` in `lib/auth/session.ts`. - Memoized `getUser`, `getUserWithTeam`, and `getTeamForUser` in `lib/db/queries.ts`. - Added conditional check for `cache` to ensure compatibility with Edge Runtime/Middleware. - Refactored functions to `export const` to support the `cache()` wrapper. - Added ⚡ OPTIMIZATION comments and updated Bolt journal. Co-authored-by: RAbuseedo <97478862+RAbuseedo@users.noreply.github.com> --- .jules/bolt.md | 5 +++++ lib/auth/session.ts | 10 ++++++++-- lib/db/queries.ts | 22 ++++++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000000..574472e64a --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,5 @@ +## 2026-04-23 - Optimizing Server-Side Data Fetching with React 'cache' + +**Learning:** React's 'cache' function is essential for deduplicating database queries and expensive computations (like JWT verification) within a single request lifecycle in Next.js. However, 'cache' is not available in the Edge Runtime (Middleware). + +**Action:** Use a conditional wrapper `(typeof cache === 'function' ? cache : (fn) => fn)` when memoizing functions that are shared between Server Components and Middleware to ensure compatibility while still gaining performance benefits where available. diff --git a/lib/auth/session.ts b/lib/auth/session.ts index 0c474c70aa..875b2c44cf 100644 --- a/lib/auth/session.ts +++ b/lib/auth/session.ts @@ -2,6 +2,7 @@ import { compare, hash } from 'bcryptjs'; import { SignJWT, jwtVerify } from 'jose'; import { cookies } from 'next/headers'; import { NewUser } from '@/lib/db/schema'; +import { cache } from 'react'; const key = new TextEncoder().encode(process.env.AUTH_SECRET); const SALT_ROUNDS = 10; @@ -30,12 +31,17 @@ export async function signToken(payload: SessionData) { .sign(key); } -export async function verifyToken(input: string) { +// ⚡ OPTIMIZATION: Memoize JWT verification to avoid redundant CPU-intensive decoding +// within a single request lifecycle. We use a conditional check for 'cache' to +// maintain compatibility with the Edge Runtime/Middleware. +export const verifyToken = (typeof cache === 'function' + ? cache + : (fn: any) => fn)(async (input: string) => { const { payload } = await jwtVerify(input, key, { algorithms: ['HS256'], }); return payload as SessionData; -} +}); export async function getSession() { const session = (await cookies()).get('session')?.value; diff --git a/lib/db/queries.ts b/lib/db/queries.ts index c607c8f746..243ad28a86 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -3,8 +3,14 @@ import { db } from './drizzle'; import { activityLogs, teamMembers, teams, users } from './schema'; import { cookies } from 'next/headers'; import { verifyToken } from '@/lib/auth/session'; +import { cache } from 'react'; -export async function getUser() { +// ⚡ OPTIMIZATION: Memoize user and team lookups to deduplicate database queries +// and JWT verifications within a single request lifecycle. We use a conditional +// check for 'cache' to maintain compatibility with the Edge Runtime/Middleware. +export const getUser = (typeof cache === 'function' + ? cache + : (fn: any) => fn)(async () => { const sessionCookie = (await cookies()).get('session'); if (!sessionCookie || !sessionCookie.value) { return null; @@ -34,7 +40,7 @@ export async function getUser() { } return user[0]; -} +}); export async function getTeamByStripeCustomerId(customerId: string) { const result = await db @@ -64,7 +70,9 @@ export async function updateTeamSubscription( .where(eq(teams.id, teamId)); } -export async function getUserWithTeam(userId: number) { +export const getUserWithTeam = (typeof cache === 'function' + ? cache + : (fn: any) => fn)(async (userId: number) => { const result = await db .select({ user: users, @@ -76,7 +84,7 @@ export async function getUserWithTeam(userId: number) { .limit(1); return result[0]; -} +}); export async function getActivityLogs() { const user = await getUser(); @@ -99,7 +107,9 @@ export async function getActivityLogs() { .limit(10); } -export async function getTeamForUser(userId: number) { +export const getTeamForUser = (typeof cache === 'function' + ? cache + : (fn: any) => fn)(async (userId: number) => { const result = await db.query.users.findFirst({ where: eq(users.id, userId), with: { @@ -126,4 +136,4 @@ export async function getTeamForUser(userId: number) { }); return result?.teamMembers[0]?.team || null; -} +});